• 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 sys
25import shutil
26import tarfile
27import hashlib
28import argparse
29import subprocess
30import ssl
31import stat
32
33sys.path.append(os.path.join(os.path.dirname(__file__), "..", "scripts"))
34
35from build.common import *
36
37EXTERNAL_DIR	= os.path.realpath(os.path.normpath(os.path.dirname(__file__)))
38
39def computeChecksum (data):
40	return hashlib.sha256(data).hexdigest()
41
42def onReadonlyRemoveError (func, path, exc_info):
43	os.chmod(path, stat.S_IWRITE)
44	os.unlink(path)
45
46class 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
60class 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
178class SourceFile (Source):
179	def __init__(self, url, filename, checksum, baseDir, extractDir = "src"):
180		Source.__init__(self, baseDir, extractDir)
181		self.url			= url
182		self.filename		= filename
183		self.checksum		= checksum
184
185	def update (self, cmdProtocol = None):
186		if not self.isFileUpToDate():
187			Source.clean(self)
188			self.fetchAndVerifyFile()
189
190	def isFileUpToDate (self):
191		file = os.path.join(EXTERNAL_DIR, pkg.baseDir, pkg.extractDir, pkg.filename)
192		if os.path.exists(file):
193			return computeChecksum(readFile(file)) == self.checksum
194		else:
195			return False
196
197	def connectToUrl (self, url):
198		result = None
199
200		if sys.version_info < (3, 0):
201			from urllib2 import urlopen
202		else:
203			from urllib.request import urlopen
204
205		if args.insecure:
206			print("Ignoring certificate checks")
207			ssl_context = ssl._create_unverified_context()
208			result = urlopen(url, context=ssl_context)
209		else:
210			result = urlopen(url)
211
212		return result
213
214	def fetchAndVerifyFile (self):
215		print("Fetching %s" % self.url)
216
217		req			= self.connectToUrl(self.url)
218		data		= req.read()
219		checksum	= computeChecksum(data)
220		dstPath		= os.path.join(EXTERNAL_DIR, self.baseDir, self.extractDir, self.filename)
221
222		if checksum != self.checksum:
223			raise Exception("Checksum mismatch for %s, expected %s, got %s" % (self.filename, self.checksum, checksum))
224
225		if not os.path.exists(os.path.dirname(dstPath)):
226			os.mkdir(os.path.dirname(dstPath))
227
228		writeFile(dstPath, data)
229
230class GitRepo (Source):
231	def __init__(self, httpsUrl, sshUrl, revision, baseDir, extractDir = "src", removeTags = []):
232		Source.__init__(self, baseDir, extractDir)
233		self.httpsUrl	= httpsUrl
234		self.sshUrl		= sshUrl
235		self.revision	= revision
236		self.removeTags	= removeTags
237
238	def detectProtocol(self, cmdProtocol = None):
239		# reuse parent repo protocol
240		proc = subprocess.Popen(['git', 'ls-remote', '--get-url', 'origin'], stdout=subprocess.PIPE)
241		(stdout, stderr) = proc.communicate()
242
243		if proc.returncode != 0:
244			raise Exception("Failed to execute 'git ls-remote origin', got %d" % proc.returncode)
245		if (stdout[:3] == 'ssh') or (stdout[:3] == 'git'):
246			protocol = 'ssh'
247		else:
248			# remote 'origin' doesn't exist, assume 'https' as checkout protocol
249			protocol = 'https'
250		return protocol
251
252	def selectUrl(self, cmdProtocol = None):
253		try:
254			if cmdProtocol == None:
255				protocol = self.detectProtocol(cmdProtocol)
256			else:
257				protocol = cmdProtocol
258		except:
259			# fallback to https on any issues
260			protocol = 'https'
261
262		if protocol == 'ssh':
263			if self.sshUrl != None:
264				url = self.sshUrl
265			else:
266				assert self.httpsUrl != None
267				url = self.httpsUrl
268		else:
269			assert protocol == 'https'
270			url = self.httpsUrl
271
272		assert url != None
273		return url
274
275	def update (self, cmdProtocol = None):
276		fullDstPath = os.path.join(EXTERNAL_DIR, self.baseDir, self.extractDir)
277
278		url = self.selectUrl(cmdProtocol)
279		if not os.path.exists(fullDstPath):
280			execute(["git", "clone", "--no-checkout", url, fullDstPath])
281
282		pushWorkingDir(fullDstPath)
283		try:
284			for tag in self.removeTags:
285				proc = subprocess.Popen(['git', 'tag', '-l', tag], stdout=subprocess.PIPE)
286				(stdout, stderr) = proc.communicate()
287				if proc.returncode == 0:
288					execute(["git", "tag", "-d",tag])
289			execute(["git", "fetch", "--tags", url, "+refs/heads/*:refs/remotes/origin/*"])
290			execute(["git", "checkout", self.revision])
291		finally:
292			popWorkingDir()
293
294def postExtractLibpng (path):
295	shutil.copy(os.path.join(path, "scripts", "pnglibconf.h.prebuilt"),
296				os.path.join(path, "pnglibconf.h"))
297
298PACKAGES = [
299	SourcePackage(
300		"http://zlib.net/zlib-1.2.11.tar.gz",
301		"zlib-1.2.11.tar.gz",
302		"c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1",
303		"zlib"),
304	SourcePackage(
305		"http://prdownloads.sourceforge.net/libpng/libpng-1.6.27.tar.gz",
306		"libpng-1.6.27.tar.gz",
307		"c9d164ec247f426a525a7b89936694aefbc91fb7a50182b198898b8fc91174b4",
308		"libpng",
309		postExtract = postExtractLibpng),
310	SourceFile(
311		"https://raw.githubusercontent.com/baldurk/renderdoc/v1.1/renderdoc/api/app/renderdoc_app.h",
312		"renderdoc_app.h",
313		"e7b5f0aa5b1b0eadc63a1c624c0ca7f5af133aa857d6a4271b0ef3d0bdb6868e",
314		"renderdoc"),
315	GitRepo(
316		"https://github.com/KhronosGroup/SPIRV-Tools.git",
317		None,
318		"e0292c269d6f5c8481afb9f2d043c74ee11ca24f",
319		"spirv-tools"),
320	GitRepo(
321		"https://github.com/KhronosGroup/glslang.git",
322		None,
323		"2898223375d57fb3974f24e1e944bb624f67cb73",
324		"glslang",
325		removeTags = ["master-tot"]),
326	GitRepo(
327		"https://github.com/KhronosGroup/SPIRV-Headers.git",
328		None,
329		"17da9f8231f78cf519b4958c2229463a63ead9e2",
330		"spirv-headers"),
331	GitRepo(
332		"https://github.com/Igalia/vkrunner.git",
333		None,
334		"2787f7ceaa96de8ad0c352629a4ed297da068872",
335		"vkrunner"),
336]
337
338def parseArgs ():
339	versionsForInsecure = ((2,7,9), (3,4,3))
340	versionsForInsecureStr = ' or '.join(('.'.join(str(x) for x in v)) for v in versionsForInsecure)
341
342	parser = argparse.ArgumentParser(description = "Fetch external sources")
343	parser.add_argument('--clean', dest='clean', action='store_true', default=False,
344						help='Remove sources instead of fetching')
345	parser.add_argument('--insecure', dest='insecure', action='store_true', default=False,
346						help="Disable certificate check for external sources."
347						" Minimum python version required " + versionsForInsecureStr)
348	parser.add_argument('--protocol', dest='protocol', default=None, choices=['ssh', 'https'],
349						help="Select protocol to checkout git repositories.")
350
351	args = parser.parse_args()
352
353	if args.insecure:
354		for versionItem in versionsForInsecure:
355			if (sys.version_info.major == versionItem[0]):
356				if sys.version_info < versionItem:
357					parser.error("For --insecure minimum required python version is " +
358								versionsForInsecureStr)
359				break;
360
361	return args
362
363if __name__ == "__main__":
364	args = parseArgs()
365
366	for pkg in PACKAGES:
367		if args.clean:
368			pkg.clean()
369		else:
370			pkg.update(args.protocol)
371