blob: ae0ffa8c97d22b5bafa0d76fa423c0c1bdeed3b1 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3"""
4BitBake 'Fetch' implementations
5
6Classes for obtaining upstream sources for the
7BitBake build tools.
8
9"""
10
11# Copyright (C) 2003, 2004 Chris Larson
12#
13# This program is free software; you can redistribute it and/or modify
14# it under the terms of the GNU General Public License version 2 as
15# published by the Free Software Foundation.
16#
17# This program is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20# GNU General Public License for more details.
21#
22# You should have received a copy of the GNU General Public License along
23# with this program; if not, write to the Free Software Foundation, Inc.,
24# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25#
26# Based on functions from the base bb module, Copyright 2003 Holger Schurig
27
28import re
29import tempfile
30import subprocess
31import os
32import logging
33import bb
Patrick Williamsc0f7c042017-02-23 20:41:17 -060034import bb.progress
35import urllib.request, urllib.parse, urllib.error
Patrick Williamsc124f4f2015-09-15 14:41:29 -050036from bb.fetch2 import FetchMethod
37from bb.fetch2 import FetchError
38from bb.fetch2 import logger
39from bb.fetch2 import runfetchcmd
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050040from bb.utils import export_proxies
Patrick Williamsc124f4f2015-09-15 14:41:29 -050041from bs4 import BeautifulSoup
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050042from bs4 import SoupStrainer
Patrick Williamsc124f4f2015-09-15 14:41:29 -050043
Patrick Williamsc0f7c042017-02-23 20:41:17 -060044class WgetProgressHandler(bb.progress.LineFilterProgressHandler):
45 """
46 Extract progress information from wget output.
47 Note: relies on --progress=dot (with -v or without -q/-nv) being
48 specified on the wget command line.
49 """
50 def __init__(self, d):
51 super(WgetProgressHandler, self).__init__(d)
52 # Send an initial progress event so the bar gets shown
53 self._fire_progress(0)
54
55 def writeline(self, line):
56 percs = re.findall(r'(\d+)%\s+([\d.]+[A-Z])', line)
57 if percs:
58 progress = int(percs[-1][0])
59 rate = percs[-1][1] + '/s'
60 self.update(progress, rate)
61 return False
62 return True
63
64
Patrick Williamsc124f4f2015-09-15 14:41:29 -050065class Wget(FetchMethod):
66 """Class to fetch urls via 'wget'"""
67 def supports(self, ud, d):
68 """
69 Check to see if a given url can be fetched with wget.
70 """
71 return ud.type in ['http', 'https', 'ftp']
72
73 def recommends_checksum(self, urldata):
74 return True
75
76 def urldata_init(self, ud, d):
77 if 'protocol' in ud.parm:
78 if ud.parm['protocol'] == 'git':
79 raise bb.fetch2.ParameterError("Invalid protocol - if you wish to fetch from a git repository using http, you need to instead use the git:// prefix with protocol=http", ud.url)
80
81 if 'downloadfilename' in ud.parm:
82 ud.basename = ud.parm['downloadfilename']
83 else:
84 ud.basename = os.path.basename(ud.path)
85
Brad Bishop6e60e8b2018-02-01 10:27:11 -050086 ud.localfile = d.expand(urllib.parse.unquote(ud.basename))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050087 if not ud.localfile:
Brad Bishop6e60e8b2018-02-01 10:27:11 -050088 ud.localfile = d.expand(urllib.parse.unquote(ud.host + ud.path).replace("/", "."))
Patrick Williamsc124f4f2015-09-15 14:41:29 -050089
Brad Bishop6e60e8b2018-02-01 10:27:11 -050090 self.basecmd = d.getVar("FETCHCMD_wget") or "/usr/bin/env wget -t 2 -T 30 --passive-ftp --no-check-certificate"
Patrick Williamsc124f4f2015-09-15 14:41:29 -050091
92 def _runwget(self, ud, d, command, quiet):
93
Patrick Williamsc0f7c042017-02-23 20:41:17 -060094 progresshandler = WgetProgressHandler(d)
95
Patrick Williamsc124f4f2015-09-15 14:41:29 -050096 logger.debug(2, "Fetching %s using command '%s'" % (ud.url, command))
Brad Bishop6e60e8b2018-02-01 10:27:11 -050097 bb.fetch2.check_network_access(d, command, ud.url)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060098 runfetchcmd(command + ' --progress=dot -v', d, quiet, log=progresshandler)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050099
100 def download(self, ud, d):
101 """Fetch urls"""
102
103 fetchcmd = self.basecmd
104
105 if 'downloadfilename' in ud.parm:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500106 dldir = d.getVar("DL_DIR")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500107 bb.utils.mkdirhier(os.path.dirname(dldir + os.sep + ud.localfile))
108 fetchcmd += " -O " + dldir + os.sep + ud.localfile
109
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500110 if ud.user and ud.pswd:
111 fetchcmd += " --user=%s --password=%s --auth-no-challenge" % (ud.user, ud.pswd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600112
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500113 uri = ud.url.split(";")[0]
114 if os.path.exists(ud.localpath):
115 # file exists, but we didnt complete it.. trying again..
116 fetchcmd += d.expand(" -c -P ${DL_DIR} '%s'" % uri)
117 else:
118 fetchcmd += d.expand(" -P ${DL_DIR} '%s'" % uri)
119
120 self._runwget(ud, d, fetchcmd, False)
121
122 # Sanity check since wget can pretend it succeed when it didn't
123 # Also, this used to happen if sourceforge sent us to the mirror page
124 if not os.path.exists(ud.localpath):
125 raise FetchError("The fetch command returned success for url %s but %s doesn't exist?!" % (uri, ud.localpath), uri)
126
127 if os.path.getsize(ud.localpath) == 0:
128 os.remove(ud.localpath)
129 raise FetchError("The fetch of %s resulted in a zero size file?! Deleting and failing since this isn't right." % (uri), uri)
130
131 return True
132
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600133 def checkstatus(self, fetch, ud, d, try_again=True):
134 import urllib.request, urllib.error, urllib.parse, socket, http.client
135 from urllib.response import addinfourl
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500136 from bb.fetch2 import FetchConnectionCache
137
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600138 class HTTPConnectionCache(http.client.HTTPConnection):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500139 if fetch.connection_cache:
140 def connect(self):
141 """Connect to the host and port specified in __init__."""
142
143 sock = fetch.connection_cache.get_connection(self.host, self.port)
144 if sock:
145 self.sock = sock
146 else:
147 self.sock = socket.create_connection((self.host, self.port),
148 self.timeout, self.source_address)
149 fetch.connection_cache.add_connection(self.host, self.port, self.sock)
150
151 if self._tunnel_host:
152 self._tunnel()
153
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600154 class CacheHTTPHandler(urllib.request.HTTPHandler):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500155 def http_open(self, req):
156 return self.do_open(HTTPConnectionCache, req)
157
158 def do_open(self, http_class, req):
159 """Return an addinfourl object for the request, using http_class.
160
161 http_class must implement the HTTPConnection API from httplib.
162 The addinfourl return value is a file-like object. It also
163 has methods and attributes including:
164 - info(): return a mimetools.Message object for the headers
165 - geturl(): return the original request URL
166 - code: HTTP status code
167 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600168 host = req.host
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500169 if not host:
170 raise urlllib2.URLError('no host given')
171
172 h = http_class(host, timeout=req.timeout) # will parse host:port
173 h.set_debuglevel(self._debuglevel)
174
175 headers = dict(req.unredirected_hdrs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600176 headers.update(dict((k, v) for k, v in list(req.headers.items())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500177 if k not in headers))
178
179 # We want to make an HTTP/1.1 request, but the addinfourl
180 # class isn't prepared to deal with a persistent connection.
181 # It will try to read all remaining data from the socket,
182 # which will block while the server waits for the next request.
183 # So make sure the connection gets closed after the (only)
184 # request.
185
186 # Don't close connection when connection_cache is enabled,
187 if fetch.connection_cache is None:
188 headers["Connection"] = "close"
189 else:
190 headers["Connection"] = "Keep-Alive" # Works for HTTP/1.0
191
192 headers = dict(
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600193 (name.title(), val) for name, val in list(headers.items()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500194
195 if req._tunnel_host:
196 tunnel_headers = {}
197 proxy_auth_hdr = "Proxy-Authorization"
198 if proxy_auth_hdr in headers:
199 tunnel_headers[proxy_auth_hdr] = headers[proxy_auth_hdr]
200 # Proxy-Authorization should not be sent to origin
201 # server.
202 del headers[proxy_auth_hdr]
203 h.set_tunnel(req._tunnel_host, headers=tunnel_headers)
204
205 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600206 h.request(req.get_method(), req.selector, req.data, headers)
207 except socket.error as err: # XXX what error?
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500208 # Don't close connection when cache is enabled.
209 if fetch.connection_cache is None:
210 h.close()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600211 raise urllib.error.URLError(err)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500212 else:
213 try:
214 r = h.getresponse(buffering=True)
215 except TypeError: # buffering kw not supported
216 r = h.getresponse()
217
218 # Pick apart the HTTPResponse object to get the addinfourl
219 # object initialized properly.
220
221 # Wrap the HTTPResponse object in socket's file object adapter
222 # for Windows. That adapter calls recv(), so delegate recv()
223 # to read(). This weird wrapping allows the returned object to
224 # have readline() and readlines() methods.
225
226 # XXX It might be better to extract the read buffering code
227 # out of socket._fileobject() and into a base class.
228 r.recv = r.read
229
230 # no data, just have to read
231 r.read()
232 class fp_dummy(object):
233 def read(self):
234 return ""
235 def readline(self):
236 return ""
237 def close(self):
238 pass
239
240 resp = addinfourl(fp_dummy(), r.msg, req.get_full_url())
241 resp.code = r.status
242 resp.msg = r.reason
243
244 # Close connection when server request it.
245 if fetch.connection_cache is not None:
246 if 'Connection' in r.msg and r.msg['Connection'] == 'close':
247 fetch.connection_cache.remove_connection(h.host, h.port)
248
249 return resp
250
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600251 class HTTPMethodFallback(urllib.request.BaseHandler):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500252 """
253 Fallback to GET if HEAD is not allowed (405 HTTP error)
254 """
255 def http_error_405(self, req, fp, code, msg, headers):
256 fp.read()
257 fp.close()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500258
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600259 newheaders = dict((k,v) for k,v in list(req.headers.items())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500260 if k.lower() not in ("content-length", "content-type"))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600261 return self.parent.open(urllib.request.Request(req.get_full_url(),
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500262 headers=newheaders,
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600263 origin_req_host=req.origin_req_host,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500264 unverifiable=True))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500265
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500266 """
267 Some servers (e.g. GitHub archives, hosted on Amazon S3) return 403
268 Forbidden when they actually mean 405 Method Not Allowed.
269 """
270 http_error_403 = http_error_405
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500271
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500272 """
273 Some servers (e.g. FusionForge) returns 406 Not Acceptable when they
274 actually mean 405 Method Not Allowed.
275 """
276 http_error_406 = http_error_405
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500277
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600278 class FixedHTTPRedirectHandler(urllib.request.HTTPRedirectHandler):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500279 """
280 urllib2.HTTPRedirectHandler resets the method to GET on redirect,
281 when we want to follow redirects using the original method.
282 """
283 def redirect_request(self, req, fp, code, msg, headers, newurl):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600284 newreq = urllib.request.HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, headers, newurl)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500285 newreq.get_method = lambda: req.get_method()
286 return newreq
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500287 exported_proxies = export_proxies(d)
288
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500289 handlers = [FixedHTTPRedirectHandler, HTTPMethodFallback]
290 if export_proxies:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600291 handlers.append(urllib.request.ProxyHandler())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500292 handlers.append(CacheHTTPHandler())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500293 # XXX: Since Python 2.7.9 ssl cert validation is enabled by default
294 # see PEP-0476, this causes verification errors on some https servers
295 # so disable by default.
296 import ssl
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500297 if hasattr(ssl, '_create_unverified_context'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600298 handlers.append(urllib.request.HTTPSHandler(context=ssl._create_unverified_context()))
299 opener = urllib.request.build_opener(*handlers)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500300
301 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500302 uri = ud.url.split(";")[0]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600303 r = urllib.request.Request(uri)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500304 r.get_method = lambda: "HEAD"
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600305
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500306 def add_basic_auth(login_str, request):
307 '''Adds Basic auth to http request, pass in login:password as string'''
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600308 import base64
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500309 encodeuser = base64.b64encode(login_str.encode('utf-8')).decode("utf-8")
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600310 authheader = "Basic %s" % encodeuser
311 r.add_header("Authorization", authheader)
312
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500313 if ud.user:
314 add_basic_auth(ud.user, r)
315
316 try:
317 import netrc, urllib.parse
318 n = netrc.netrc()
319 login, unused, password = n.authenticators(urllib.parse.urlparse(uri).hostname)
320 add_basic_auth("%s:%s" % (login, password), r)
321 except (TypeError, ImportError, IOError, netrc.NetrcParseError):
322 pass
323
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500324 opener.open(r)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600325 except urllib.error.URLError as e:
326 if try_again:
327 logger.debug(2, "checkstatus: trying again")
328 return self.checkstatus(fetch, ud, d, False)
329 else:
330 # debug for now to avoid spamming the logs in e.g. remote sstate searches
331 logger.debug(2, "checkstatus() urlopen failed: %s" % e)
332 return False
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500333 return True
334
335 def _parse_path(self, regex, s):
336 """
337 Find and group name, version and archive type in the given string s
338 """
339
340 m = regex.search(s)
341 if m:
342 pname = ''
343 pver = ''
344 ptype = ''
345
346 mdict = m.groupdict()
347 if 'name' in mdict.keys():
348 pname = mdict['name']
349 if 'pver' in mdict.keys():
350 pver = mdict['pver']
351 if 'type' in mdict.keys():
352 ptype = mdict['type']
353
354 bb.debug(3, "_parse_path: %s, %s, %s" % (pname, pver, ptype))
355
356 return (pname, pver, ptype)
357
358 return None
359
360 def _modelate_version(self, version):
361 if version[0] in ['.', '-']:
362 if version[1].isdigit():
363 version = version[1] + version[0] + version[2:len(version)]
364 else:
365 version = version[1:len(version)]
366
367 version = re.sub('-', '.', version)
368 version = re.sub('_', '.', version)
369 version = re.sub('(rc)+', '.1000.', version)
370 version = re.sub('(beta)+', '.100.', version)
371 version = re.sub('(alpha)+', '.10.', version)
372 if version[0] == 'v':
373 version = version[1:len(version)]
374 return version
375
376 def _vercmp(self, old, new):
377 """
378 Check whether 'new' is newer than 'old' version. We use existing vercmp() for the
379 purpose. PE is cleared in comparison as it's not for build, and PR is cleared too
380 for simplicity as it's somehow difficult to get from various upstream format
381 """
382
383 (oldpn, oldpv, oldsuffix) = old
384 (newpn, newpv, newsuffix) = new
385
386 """
387 Check for a new suffix type that we have never heard of before
388 """
389 if (newsuffix):
390 m = self.suffix_regex_comp.search(newsuffix)
391 if not m:
392 bb.warn("%s has a possible unknown suffix: %s" % (newpn, newsuffix))
393 return False
394
395 """
396 Not our package so ignore it
397 """
398 if oldpn != newpn:
399 return False
400
401 oldpv = self._modelate_version(oldpv)
402 newpv = self._modelate_version(newpv)
403
404 return bb.utils.vercmp(("0", oldpv, ""), ("0", newpv, ""))
405
406 def _fetch_index(self, uri, ud, d):
407 """
408 Run fetch checkstatus to get directory information
409 """
410 f = tempfile.NamedTemporaryFile()
411
412 agent = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.12) Gecko/20101027 Ubuntu/9.10 (karmic) Firefox/3.6.12"
413 fetchcmd = self.basecmd
414 fetchcmd += " -O " + f.name + " --user-agent='" + agent + "' '" + uri + "'"
415 try:
416 self._runwget(ud, d, fetchcmd, True)
417 fetchresult = f.read()
418 except bb.fetch2.BBFetchException:
419 fetchresult = ""
420
421 f.close()
422 return fetchresult
423
424 def _check_latest_version(self, url, package, package_regex, current_version, ud, d):
425 """
426 Return the latest version of a package inside a given directory path
427 If error or no version, return ""
428 """
429 valid = 0
430 version = ['', '', '']
431
432 bb.debug(3, "VersionURL: %s" % (url))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500433 soup = BeautifulSoup(self._fetch_index(url, ud, d), "html.parser", parse_only=SoupStrainer("a"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500434 if not soup:
435 bb.debug(3, "*** %s NO SOUP" % (url))
436 return ""
437
438 for line in soup.find_all('a', href=True):
439 bb.debug(3, "line['href'] = '%s'" % (line['href']))
440 bb.debug(3, "line = '%s'" % (str(line)))
441
442 newver = self._parse_path(package_regex, line['href'])
443 if not newver:
444 newver = self._parse_path(package_regex, str(line))
445
446 if newver:
447 bb.debug(3, "Upstream version found: %s" % newver[1])
448 if valid == 0:
449 version = newver
450 valid = 1
451 elif self._vercmp(version, newver) < 0:
452 version = newver
453
454 pupver = re.sub('_', '.', version[1])
455
456 bb.debug(3, "*** %s -> UpstreamVersion = %s (CurrentVersion = %s)" %
457 (package, pupver or "N/A", current_version[1]))
458
459 if valid:
460 return pupver
461
462 return ""
463
464 def _check_latest_version_by_dir(self, dirver, package, package_regex,
465 current_version, ud, d):
466 """
467 Scan every directory in order to get upstream version.
468 """
469 version_dir = ['', '', '']
470 version = ['', '', '']
471
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500472 dirver_regex = re.compile("(?P<pfx>\D*)(?P<ver>(\d+[\.\-_])+(\d+))")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500473 s = dirver_regex.search(dirver)
474 if s:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500475 version_dir[1] = s.group('ver')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500476 else:
477 version_dir[1] = dirver
478
479 dirs_uri = bb.fetch.encodeurl([ud.type, ud.host,
480 ud.path.split(dirver)[0], ud.user, ud.pswd, {}])
481 bb.debug(3, "DirURL: %s, %s" % (dirs_uri, package))
482
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500483 soup = BeautifulSoup(self._fetch_index(dirs_uri, ud, d), "html.parser", parse_only=SoupStrainer("a"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500484 if not soup:
485 return version[1]
486
487 for line in soup.find_all('a', href=True):
488 s = dirver_regex.search(line['href'].strip("/"))
489 if s:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500490 sver = s.group('ver')
491
492 # When prefix is part of the version directory it need to
493 # ensure that only version directory is used so remove previous
494 # directories if exists.
495 #
496 # Example: pfx = '/dir1/dir2/v' and version = '2.5' the expected
497 # result is v2.5.
498 spfx = s.group('pfx').split('/')[-1]
499
500 version_dir_new = ['', sver, '']
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500501 if self._vercmp(version_dir, version_dir_new) <= 0:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500502 dirver_new = spfx + sver
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500503 path = ud.path.replace(dirver, dirver_new, True) \
504 .split(package)[0]
505 uri = bb.fetch.encodeurl([ud.type, ud.host, path,
506 ud.user, ud.pswd, {}])
507
508 pupver = self._check_latest_version(uri,
509 package, package_regex, current_version, ud, d)
510 if pupver:
511 version[1] = pupver
512
513 version_dir = version_dir_new
514
515 return version[1]
516
517 def _init_regexes(self, package, ud, d):
518 """
519 Match as many patterns as possible such as:
520 gnome-common-2.20.0.tar.gz (most common format)
521 gtk+-2.90.1.tar.gz
522 xf86-input-synaptics-12.6.9.tar.gz
523 dri2proto-2.3.tar.gz
524 blktool_4.orig.tar.gz
525 libid3tag-0.15.1b.tar.gz
526 unzip552.tar.gz
527 icu4c-3_6-src.tgz
528 genext2fs_1.3.orig.tar.gz
529 gst-fluendo-mp3
530 """
531 # match most patterns which uses "-" as separator to version digits
532 pn_prefix1 = "[a-zA-Z][a-zA-Z0-9]*([-_][a-zA-Z]\w+)*\+?[-_]"
533 # a loose pattern such as for unzip552.tar.gz
534 pn_prefix2 = "[a-zA-Z]+"
535 # a loose pattern such as for 80325-quicky-0.4.tar.gz
536 pn_prefix3 = "[0-9]+[-]?[a-zA-Z]+"
537 # Save the Package Name (pn) Regex for use later
538 pn_regex = "(%s|%s|%s)" % (pn_prefix1, pn_prefix2, pn_prefix3)
539
540 # match version
541 pver_regex = "(([A-Z]*\d+[a-zA-Z]*[\.\-_]*)+)"
542
543 # match arch
544 parch_regex = "-source|_all_"
545
546 # src.rpm extension was added only for rpm package. Can be removed if the rpm
547 # packaged will always be considered as having to be manually upgraded
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500548 psuffix_regex = "(tar\.gz|tgz|tar\.bz2|zip|xz|tar\.lz|rpm|bz2|orig\.tar\.gz|tar\.xz|src\.tar\.gz|src\.tgz|svnr\d+\.tar\.bz2|stable\.tar\.gz|src\.rpm)"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500549
550 # match name, version and archive type of a package
551 package_regex_comp = re.compile("(?P<name>%s?\.?v?)(?P<pver>%s)(?P<arch>%s)?[\.-](?P<type>%s$)"
552 % (pn_regex, pver_regex, parch_regex, psuffix_regex))
553 self.suffix_regex_comp = re.compile(psuffix_regex)
554
555 # compile regex, can be specific by package or generic regex
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500556 pn_regex = d.getVar('UPSTREAM_CHECK_REGEX')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500557 if pn_regex:
558 package_custom_regex_comp = re.compile(pn_regex)
559 else:
560 version = self._parse_path(package_regex_comp, package)
561 if version:
562 package_custom_regex_comp = re.compile(
563 "(?P<name>%s)(?P<pver>%s)(?P<arch>%s)?[\.-](?P<type>%s)" %
564 (re.escape(version[0]), pver_regex, parch_regex, psuffix_regex))
565 else:
566 package_custom_regex_comp = None
567
568 return package_custom_regex_comp
569
570 def latest_versionstring(self, ud, d):
571 """
572 Manipulate the URL and try to obtain the latest package version
573
574 sanity check to ensure same name and type.
575 """
576 package = ud.path.split("/")[-1]
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500577 current_version = ['', d.getVar('PV'), '']
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500578
579 """possible to have no version in pkg name, such as spectrum-fw"""
580 if not re.search("\d+", package):
581 current_version[1] = re.sub('_', '.', current_version[1])
582 current_version[1] = re.sub('-', '.', current_version[1])
583 return (current_version[1], '')
584
585 package_regex = self._init_regexes(package, ud, d)
586 if package_regex is None:
587 bb.warn("latest_versionstring: package %s don't match pattern" % (package))
588 return ('', '')
589 bb.debug(3, "latest_versionstring, regex: %s" % (package_regex.pattern))
590
591 uri = ""
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500592 regex_uri = d.getVar("UPSTREAM_CHECK_URI")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500593 if not regex_uri:
594 path = ud.path.split(package)[0]
595
596 # search for version matches on folders inside the path, like:
597 # "5.7" in http://download.gnome.org/sources/${PN}/5.7/${PN}-${PV}.tar.gz
598 dirver_regex = re.compile("(?P<dirver>[^/]*(\d+\.)*\d+([-_]r\d+)*)/")
599 m = dirver_regex.search(path)
600 if m:
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500601 pn = d.getVar('PN')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500602 dirver = m.group('dirver')
603
604 dirver_pn_regex = re.compile("%s\d?" % (re.escape(pn)))
605 if not dirver_pn_regex.search(dirver):
606 return (self._check_latest_version_by_dir(dirver,
607 package, package_regex, current_version, ud, d), '')
608
609 uri = bb.fetch.encodeurl([ud.type, ud.host, path, ud.user, ud.pswd, {}])
610 else:
611 uri = regex_uri
612
613 return (self._check_latest_version(uri, package, package_regex,
614 current_version, ud, d), '')