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 urllib2
28import hashlib
29import argparse
30import subprocess
31
32sys.path.append(os.path.join(os.path.dirname(__file__), "..", "scripts"))
33
34from build.common import *
35
36EXTERNAL_DIR	= os.path.realpath(os.path.normpath(os.path.dirname(__file__)))
37
38def computeChecksum (data):
39	return hashlib.sha256(data).hexdigest()
40
41class Source:
42	def __init__(self, baseDir, extractDir):
43		self.baseDir		= baseDir
44		self.extractDir		= extractDir
45
46	def clean (self):
47		fullDstPath = os.path.join(EXTERNAL_DIR, self.baseDir, self.extractDir)
48		if os.path.exists(fullDstPath):
49			shutil.rmtree(fullDstPath, ignore_errors=False)
50
51class SourcePackage (Source):
52	def __init__(self, url, filename, checksum, baseDir, extractDir = "src", postExtract=None):
53		Source.__init__(self, baseDir, extractDir)
54		self.url			= url
55		self.filename		= filename
56		self.checksum		= checksum
57		self.archiveDir		= "packages"
58		self.postExtract	= postExtract
59
60	def clean (self):
61		Source.clean(self)
62		self.removeArchives()
63
64	def update (self):
65		if not self.isArchiveUpToDate():
66			self.fetchAndVerifyArchive()
67
68		if self.getExtractedChecksum() != self.checksum:
69			Source.clean(self)
70			self.extract()
71			self.storeExtractedChecksum(self.checksum)
72
73	def removeArchives (self):
74		archiveDir = os.path.join(EXTERNAL_DIR, pkg.baseDir, pkg.archiveDir)
75		if os.path.exists(archiveDir):
76			shutil.rmtree(archiveDir, ignore_errors=False)
77
78	def isArchiveUpToDate (self):
79		archiveFile = os.path.join(EXTERNAL_DIR, pkg.baseDir, pkg.archiveDir, pkg.filename)
80		if os.path.exists(archiveFile):
81			return computeChecksum(readFile(archiveFile)) == self.checksum
82		else:
83			return False
84
85	def getExtractedChecksumFilePath (self):
86		return os.path.join(EXTERNAL_DIR, pkg.baseDir, pkg.archiveDir, "extracted")
87
88	def getExtractedChecksum (self):
89		extractedChecksumFile = self.getExtractedChecksumFilePath()
90
91		if os.path.exists(extractedChecksumFile):
92			return readFile(extractedChecksumFile)
93		else:
94			return None
95
96	def storeExtractedChecksum (self, checksum):
97		writeFile(self.getExtractedChecksumFilePath(), checksum)
98
99	def fetchAndVerifyArchive (self):
100		print "Fetching %s" % self.url
101
102		req			= urllib2.urlopen(self.url)
103		data		= req.read()
104		checksum	= computeChecksum(data)
105		dstPath		= os.path.join(EXTERNAL_DIR, self.baseDir, self.archiveDir, self.filename)
106
107		if checksum != self.checksum:
108			raise Exception("Checksum mismatch for %s, expected %s, got %s" % (self.filename, self.checksum, checksum))
109
110		if not os.path.exists(os.path.dirname(dstPath)):
111			os.mkdir(os.path.dirname(dstPath))
112
113		writeFile(dstPath, data)
114
115	def extract (self):
116		print "Extracting %s to %s/%s" % (self.filename, self.baseDir, self.extractDir)
117
118		srcPath	= os.path.join(EXTERNAL_DIR, self.baseDir, self.archiveDir, self.filename)
119		tmpPath	= os.path.join(EXTERNAL_DIR, ".extract-tmp-%s" % self.baseDir)
120		dstPath	= os.path.join(EXTERNAL_DIR, self.baseDir, self.extractDir)
121		archive	= tarfile.open(srcPath)
122
123		if os.path.exists(tmpPath):
124			shutil.rmtree(tmpPath, ignore_errors=False)
125
126		os.mkdir(tmpPath)
127
128		archive.extractall(tmpPath)
129		archive.close()
130
131		extractedEntries = os.listdir(tmpPath)
132		if len(extractedEntries) != 1 or not os.path.isdir(os.path.join(tmpPath, extractedEntries[0])):
133			raise Exception("%s doesn't contain single top-level directory" % self.filename)
134
135		topLevelPath = os.path.join(tmpPath, extractedEntries[0])
136
137		if not os.path.exists(dstPath):
138			os.mkdir(dstPath)
139
140		for entry in os.listdir(topLevelPath):
141			if os.path.exists(os.path.join(dstPath, entry)):
142				raise Exception("%s exists already" % entry)
143
144			shutil.move(os.path.join(topLevelPath, entry), dstPath)
145
146		shutil.rmtree(tmpPath, ignore_errors=True)
147
148		if self.postExtract != None:
149			self.postExtract(dstPath)
150
151class GitRepo (Source):
152	def __init__(self, url, revision, baseDir, extractDir = "src"):
153		Source.__init__(self, baseDir, extractDir)
154		self.url		= url
155		self.revision	= revision
156
157	def update (self):
158		fullDstPath = os.path.join(EXTERNAL_DIR, self.baseDir, self.extractDir)
159
160		if not os.path.exists(fullDstPath):
161			execute(["git", "clone", "--no-checkout", self.url, fullDstPath])
162
163		pushWorkingDir(fullDstPath)
164		try:
165			execute(["git", "fetch", self.url, "+refs/heads/*:refs/remotes/origin/*"])
166			execute(["git", "checkout", self.revision])
167		finally:
168			popWorkingDir()
169
170def postExtractLibpng (path):
171	shutil.copy(os.path.join(path, "scripts", "pnglibconf.h.prebuilt"),
172				os.path.join(path, "pnglibconf.h"))
173
174PACKAGES = [
175	SourcePackage(
176		"http://zlib.net/zlib-1.2.11.tar.gz",
177		"zlib-1.2.11.tar.gz",
178		"c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1",
179		"zlib"),
180	SourcePackage(
181		"http://prdownloads.sourceforge.net/libpng/libpng-1.6.27.tar.gz",
182		"libpng-1.6.27.tar.gz",
183		"c9d164ec247f426a525a7b89936694aefbc91fb7a50182b198898b8fc91174b4",
184		"libpng",
185		postExtract = postExtractLibpng),
186	GitRepo(
187		"https://github.com/KhronosGroup/SPIRV-Tools.git",
188		"0b0454c42c6b6f6746434bd5c78c5c70f65d9c51",
189		"spirv-tools"),
190	GitRepo(
191		"https://github.com/KhronosGroup/glslang.git",
192		"a5c5fb61180e8703ca85f36d618f98e16dc317e2",
193		"glslang"),
194	GitRepo(
195		"https://github.com/KhronosGroup/SPIRV-Headers.git",
196		"2bf02308656f97898c5f7e433712f21737c61e4e",
197		"spirv-headers"),
198]
199
200def parseArgs ():
201	parser = argparse.ArgumentParser(description = "Fetch external sources")
202	parser.add_argument('--clean', dest='clean', action='store_true', default=False,
203						help='Remove sources instead of fetching')
204	return parser.parse_args()
205
206if __name__ == "__main__":
207	args = parseArgs()
208
209	for pkg in PACKAGES:
210		if args.clean:
211			pkg.clean()
212		else:
213			pkg.update()
214