wrap.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. # Copyright 2015 The Meson development team
  2. # Licensed under the Apache License, Version 2.0 (the "License");
  3. # you may not use this file except in compliance with the License.
  4. # You may obtain a copy of the License at
  5. # http://www.apache.org/licenses/LICENSE-2.0
  6. # Unless required by applicable law or agreed to in writing, software
  7. # distributed under the License is distributed on an "AS IS" BASIS,
  8. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. # See the License for the specific language governing permissions and
  10. # limitations under the License.
  11. from .. import mlog
  12. import contextlib
  13. import urllib.request, os, hashlib, shutil
  14. import subprocess
  15. import sys
  16. try:
  17. import ssl
  18. has_ssl = True
  19. API_ROOT = 'https://wrapdb.mesonbuild.com/v1/'
  20. except ImportError:
  21. has_ssl = False
  22. API_ROOT = 'http://wrapdb.mesonbuild.com/v1/'
  23. ssl_warning_printed = False
  24. def build_ssl_context():
  25. ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
  26. ctx.options |= ssl.OP_NO_SSLv2
  27. ctx.options |= ssl.OP_NO_SSLv3
  28. ctx.verify_mode = ssl.CERT_REQUIRED
  29. ctx.load_default_certs()
  30. return ctx
  31. def open_wrapdburl(urlstring):
  32. global ssl_warning_printed
  33. if has_ssl:
  34. try:
  35. return urllib.request.urlopen(urlstring)#, context=build_ssl_context())
  36. except urllib.error.URLError:
  37. if not ssl_warning_printed:
  38. print('SSL connection failed. Falling back to unencrypted connections.')
  39. ssl_warning_printed = True
  40. if not ssl_warning_printed:
  41. print('Warning: SSL not available, traffic not authenticated.',
  42. file=sys.stderr)
  43. ssl_warning_printed = True
  44. # Trying to open SSL connection to wrapdb fails because the
  45. # certificate is not known.
  46. if urlstring.startswith('https'):
  47. urlstring = 'http' + urlstring[5:]
  48. return urllib.request.urlopen(urlstring)
  49. class PackageDefinition:
  50. def __init__(self, fname):
  51. self.values = {}
  52. with open(fname) as ifile:
  53. first = ifile.readline().strip()
  54. if first == '[wrap-file]':
  55. self.type = 'file'
  56. elif first == '[wrap-git]':
  57. self.type = 'git'
  58. else:
  59. raise RuntimeError('Invalid format of package file')
  60. for line in ifile:
  61. line = line.strip()
  62. if line == '':
  63. continue
  64. (k, v) = line.split('=', 1)
  65. k = k.strip()
  66. v = v.strip()
  67. self.values[k] = v
  68. def get(self, key):
  69. return self.values[key]
  70. def has_patch(self):
  71. return 'patch_url' in self.values
  72. class Resolver:
  73. def __init__(self, subdir_root):
  74. self.subdir_root = subdir_root
  75. self.cachedir = os.path.join(self.subdir_root, 'packagecache')
  76. def resolve(self, packagename):
  77. fname = os.path.join(self.subdir_root, packagename + '.wrap')
  78. dirname = os.path.join(self.subdir_root, packagename)
  79. if os.path.isdir(dirname):
  80. # The directory is there? Great, use it.
  81. return packagename
  82. if not os.path.isfile(fname):
  83. # No wrap file with this name? Give up.
  84. return None
  85. p = PackageDefinition(fname)
  86. if p.type == 'file':
  87. if not os.path.isdir(self.cachedir):
  88. os.mkdir(self.cachedir)
  89. self.download(p, packagename)
  90. self.extract_package(p)
  91. elif p.type == 'git':
  92. self.get_git(p)
  93. else:
  94. raise RuntimeError('Unreachable code.')
  95. return p.get('directory')
  96. def get_git(self, p):
  97. checkoutdir = os.path.join(self.subdir_root, p.get('directory'))
  98. revno = p.get('revision')
  99. is_there = os.path.isdir(checkoutdir)
  100. if is_there:
  101. if revno.lower() == 'head':
  102. # Failure to do pull is not a fatal error,
  103. # because otherwise you can't develop without
  104. # a working net connection.
  105. subprocess.call(['git', 'pull'], cwd=checkoutdir)
  106. else:
  107. if subprocess.call(['git', 'checkout', revno], cwd=checkoutdir) != 0:
  108. subprocess.check_call(['git', 'fetch'], cwd=checkoutdir)
  109. subprocess.check_call(['git', 'checkout', revno],
  110. cwd=checkoutdir)
  111. else:
  112. subprocess.check_call(['git', 'clone', p.get('url'),
  113. p.get('directory')], cwd=self.subdir_root)
  114. if revno.lower() != 'head':
  115. subprocess.check_call(['git', 'checkout', revno],
  116. cwd=checkoutdir)
  117. def get_data(self, url):
  118. blocksize = 10*1024
  119. if url.startswith('https://wrapdb.mesonbuild.com'):
  120. resp = open_wrapdburl(url)
  121. else:
  122. resp = urllib.request.urlopen(url)
  123. with contextlib.closing(resp) as resp:
  124. try:
  125. dlsize = int(resp.info()['Content-Length'])
  126. except TypeError:
  127. dlsize = None
  128. if dlsize is None:
  129. print('Downloading file of unknown size.')
  130. return resp.read()
  131. print('Download size:', dlsize)
  132. print('Downloading: ', end='')
  133. sys.stdout.flush()
  134. printed_dots = 0
  135. blocks = []
  136. downloaded = 0
  137. while True:
  138. block = resp.read(blocksize)
  139. if block == b'':
  140. break
  141. downloaded += len(block)
  142. blocks.append(block)
  143. ratio = int(downloaded/dlsize * 10)
  144. while printed_dots < ratio:
  145. print('.', end='')
  146. sys.stdout.flush()
  147. printed_dots += 1
  148. print('')
  149. return b''.join(blocks)
  150. def get_hash(self, data):
  151. h = hashlib.sha256()
  152. h.update(data)
  153. hashvalue = h.hexdigest()
  154. return hashvalue
  155. def download(self, p, packagename):
  156. ofname = os.path.join(self.cachedir, p.get('source_filename'))
  157. if os.path.exists(ofname):
  158. mlog.log('Using', mlog.bold(packagename), 'from cache.')
  159. return
  160. srcurl = p.get('source_url')
  161. mlog.log('Dowloading', mlog.bold(packagename), 'from', mlog.bold(srcurl))
  162. srcdata = self.get_data(srcurl)
  163. dhash = self.get_hash(srcdata)
  164. expected = p.get('source_hash')
  165. if dhash != expected:
  166. raise RuntimeError('Incorrect hash for source %s:\n %s expected\n %s actual.' % (packagename, expected, dhash))
  167. with open(ofname, 'wb') as f:
  168. f.write(srcdata)
  169. if p.has_patch():
  170. purl = p.get('patch_url')
  171. mlog.log('Downloading patch from', mlog.bold(purl))
  172. pdata = self.get_data(purl)
  173. phash = self.get_hash(pdata)
  174. expected = p.get('patch_hash')
  175. if phash != expected:
  176. raise RuntimeError('Incorrect hash for patch %s:\n %s expected\n %s actual' % (packagename, expected, phash))
  177. filename = os.path.join(self.cachedir, p.get('patch_filename'))
  178. with open(filename, 'wb') as f:
  179. f.write(pdata)
  180. else:
  181. mlog.log('Package does not require patch.')
  182. def extract_package(self, package):
  183. if sys.version_info < (3, 5):
  184. try:
  185. import lzma
  186. del lzma
  187. try:
  188. shutil.register_unpack_format('xztar', ['.tar.xz', '.txz'], shutil._unpack_tarfile, [], "xz'ed tar-file")
  189. except shutil.RegistryError:
  190. pass
  191. except ImportError:
  192. pass
  193. target_dir = os.path.join(self.subdir_root, package.get('directory'))
  194. if os.path.isdir(target_dir):
  195. return
  196. extract_dir = self.subdir_root
  197. # Some upstreams ship packages that do not have a leading directory.
  198. # Create one for them.
  199. try:
  200. package.get('lead_directory_missing')
  201. os.mkdir(target_dir)
  202. extract_dir = target_dir
  203. except KeyError:
  204. pass
  205. shutil.unpack_archive(os.path.join(self.cachedir, package.get('source_filename')), extract_dir)
  206. if package.has_patch():
  207. shutil.unpack_archive(os.path.join(self.cachedir, package.get('patch_filename')), self.subdir_root)