Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sage
Path: blob/develop/build/sage_bootstrap/tarball.py
4052 views
1
# -*- coding: utf-8 -*-
2
"""
3
Third-Party Tarballs
4
"""
5
6
# ****************************************************************************
7
# Copyright (C) 2014-2015 Volker Braun <[email protected]>
8
# 2017 Jeroen Demeyer
9
# 2020 Matthias Koeppe
10
#
11
# This program is free software: you can redistribute it and/or modify
12
# it under the terms of the GNU General Public License as published by
13
# the Free Software Foundation, either version 2 of the License, or
14
# (at your option) any later version.
15
# https://www.gnu.org/licenses/
16
# ****************************************************************************
17
18
import os
19
import logging
20
log = logging.getLogger()
21
22
from sage_bootstrap.env import SAGE_DISTFILES
23
from sage_bootstrap.download import Download, MirrorList
24
from sage_bootstrap.package import Package
25
26
27
class ChecksumError(Exception):
28
"""
29
Exception raised when the checksum of the tarball does not match
30
"""
31
pass
32
33
34
class FileNotMirroredError(Exception):
35
"""
36
Exception raised when the tarball cannot be downloaded from the mirrors
37
"""
38
pass
39
40
41
class Tarball(object):
42
43
def __init__(self, tarball_name, package=None):
44
"""
45
A (third-party downloadable) tarball
46
47
Note that the tarball might also be a different kind of
48
archive format that is supported, it does not necessarily have
49
to be tar.
50
51
INPUT:
52
53
- ``tarball_name`` - string. The full filename (``foo-1.3.tar.bz2``)
54
of a tarball on the Sage mirror network.
55
"""
56
self.__filename = tarball_name
57
if package is None:
58
self.__package = None
59
for pkg in Package.all():
60
if pkg.tarball_filename == tarball_name:
61
self.__package = pkg.tarball_package
62
if self.package is None:
63
error = 'tarball {0} is not referenced by any Sage package'.format(tarball_name)
64
log.error(error)
65
raise ValueError(error)
66
else:
67
self.__package = package
68
if package.tarball_filename != tarball_name:
69
error = 'tarball {0} is not referenced by the {1} package'.format(tarball_name, package.name)
70
log.error(error)
71
raise ValueError(error)
72
73
def __repr__(self):
74
return 'Tarball {0}'.format(self.filename)
75
76
@property
77
def filename(self):
78
"""
79
Return the tarball filename
80
81
OUTPUT:
82
83
String. The full filename (``foo-1.3.tar.bz2``) of the
84
tarball.
85
"""
86
return self.__filename
87
88
@property
89
def package(self):
90
"""
91
Return the package that the tarball belongs to
92
93
OUTPUT:
94
95
Instance of :class:`sage_bootstrap.package.Package`
96
"""
97
return self.__package
98
99
@property
100
def upstream_fqn(self):
101
"""
102
The fully-qualified (including directory) file name in the upstream directory.
103
"""
104
return os.path.join(SAGE_DISTFILES, self.filename)
105
106
def __eq__(self, other):
107
return self.filename == other.filename
108
109
def _compute_hash(self, algorithm):
110
with open(self.upstream_fqn, 'rb') as f:
111
while True:
112
buf = f.read(0x100000)
113
if not buf:
114
break
115
algorithm.update(buf)
116
return algorithm.hexdigest()
117
118
def _compute_sha1(self):
119
import hashlib
120
return self._compute_hash(hashlib.sha1())
121
122
def _compute_sha256(self):
123
import hashlib
124
return self._compute_hash(hashlib.sha256())
125
126
def checksum_verifies(self, force_sha256=False):
127
"""
128
Test whether the checksum of the downloaded file is correct.
129
"""
130
if self.package.sha256:
131
sha256 = self._compute_sha256()
132
if sha256 != self.package.sha256:
133
return False
134
elif force_sha256:
135
log.warning('sha256 not available for {0}'.format(self.package.name))
136
return False
137
else:
138
log.warning('sha256 not available for {0}, using sha1'.format(self.package.name))
139
sha1 = self._compute_sha1()
140
return sha1 == self.package.sha1
141
142
def is_distributable(self):
143
return 'do-not-distribute' not in self.filename
144
145
def download(self, allow_upstream=False):
146
"""
147
Download the tarball to the upstream directory.
148
149
If allow_upstream is False and the package cannot be found
150
on the sage mirrors, fall back to downloading it from
151
the upstream URL if the package has one.
152
"""
153
if not self.filename:
154
raise ValueError('non-normal package does define a tarball, so cannot download')
155
destination = self.upstream_fqn
156
if os.path.isfile(destination):
157
if self.checksum_verifies():
158
log.info('Using cached file {destination}'.format(destination=destination))
159
return
160
else:
161
# Garbage in the upstream directory? Ignore it.
162
# Don't delete it because maybe somebody just forgot to
163
# update the checksum (Issue #23972).
164
log.warning('Invalid checksum; ignoring cached file {destination}'
165
.format(destination=destination))
166
successful_download = False
167
log.info('Attempting to download package {0} from mirrors'.format(self.filename))
168
for mirror in MirrorList():
169
url = mirror.replace('${SPKG}', self.package.name)
170
if not url.endswith('/'):
171
url += '/'
172
url += self.filename
173
log.info(url)
174
try:
175
Download(url, destination).run()
176
successful_download = True
177
break
178
except IOError:
179
log.debug('File not on mirror')
180
if not successful_download:
181
url = self.package.tarball_upstream_url
182
if allow_upstream and url:
183
log.info('Attempting to download from {}'.format(url))
184
try:
185
Download(url, destination).run()
186
except IOError:
187
raise FileNotMirroredError('tarball does not exist on mirror network and neither at the upstream URL')
188
else:
189
raise FileNotMirroredError('tarball does not exist on mirror network')
190
if not self.checksum_verifies():
191
raise ChecksumError('checksum does not match')
192
193
def save_as(self, destination):
194
"""
195
Save the tarball as a new file
196
"""
197
import shutil
198
shutil.copy(self.upstream_fqn, destination)
199
200