blob: 23d48acb0779cc49934936b574a1f1b363b6430c [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 import data
37from bb.fetch2 import FetchMethod
38from bb.fetch2 import FetchError
39from bb.fetch2 import logger
40from bb.fetch2 import runfetchcmd
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050041from bb.utils import export_proxies
Patrick Williamsc124f4f2015-09-15 14:41:29 -050042from bs4 import BeautifulSoup
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050043from bs4 import SoupStrainer
Patrick Williamsc124f4f2015-09-15 14:41:29 -050044
Patrick Williamsc0f7c042017-02-23 20:41:17 -060045class WgetProgressHandler(bb.progress.LineFilterProgressHandler):
46 """
47 Extract progress information from wget output.
48 Note: relies on --progress=dot (with -v or without -q/-nv) being
49 specified on the wget command line.
50 """
51 def __init__(self, d):
52 super(WgetProgressHandler, self).__init__(d)
53 # Send an initial progress event so the bar gets shown
54 self._fire_progress(0)
55
56 def writeline(self, line):
57 percs = re.findall(r'(\d+)%\s+([\d.]+[A-Z])', line)
58 if percs:
59 progress = int(percs[-1][0])
60 rate = percs[-1][1] + '/s'
61 self.update(progress, rate)
62 return False
63 return True
64
65
Patrick Williamsc124f4f2015-09-15 14:41:29 -050066class Wget(FetchMethod):
67 """Class to fetch urls via 'wget'"""
68 def supports(self, ud, d):
69 """
70 Check to see if a given url can be fetched with wget.
71 """
72 return ud.type in ['http', 'https', 'ftp']
73
74 def recommends_checksum(self, urldata):
75 return True
76
77 def urldata_init(self, ud, d):
78 if 'protocol' in ud.parm:
79 if ud.parm['protocol'] == 'git':
80 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)
81
82 if 'downloadfilename' in ud.parm:
83 ud.basename = ud.parm['downloadfilename']
84 else:
85 ud.basename = os.path.basename(ud.path)
86
Patrick Williamsc0f7c042017-02-23 20:41:17 -060087 ud.localfile = data.expand(urllib.parse.unquote(ud.basename), d)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050088 if not ud.localfile:
Patrick Williamsc0f7c042017-02-23 20:41:17 -060089 ud.localfile = data.expand(urllib.parse.unquote(ud.host + ud.path).replace("/", "."), d)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050090
Patrick Williamsc0f7c042017-02-23 20:41:17 -060091 self.basecmd = d.getVar("FETCHCMD_wget", True) or "/usr/bin/env wget -t 2 -T 30 --passive-ftp --no-check-certificate"
Patrick Williamsc124f4f2015-09-15 14:41:29 -050092
93 def _runwget(self, ud, d, command, quiet):
94
Patrick Williamsc0f7c042017-02-23 20:41:17 -060095 progresshandler = WgetProgressHandler(d)
96
Patrick Williamsc124f4f2015-09-15 14:41:29 -050097 logger.debug(2, "Fetching %s using command '%s'" % (ud.url, command))
98 bb.fetch2.check_network_access(d, command)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060099 runfetchcmd(command + ' --progress=dot -v', d, quiet, log=progresshandler)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500100
101 def download(self, ud, d):
102 """Fetch urls"""
103
104 fetchcmd = self.basecmd
105
106 if 'downloadfilename' in ud.parm:
107 dldir = d.getVar("DL_DIR", True)
108 bb.utils.mkdirhier(os.path.dirname(dldir + os.sep + ud.localfile))
109 fetchcmd += " -O " + dldir + os.sep + ud.localfile
110
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500111 if ud.user and ud.pswd:
112 fetchcmd += " --user=%s --password=%s --auth-no-challenge" % (ud.user, ud.pswd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600113
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500114 uri = ud.url.split(";")[0]
115 if os.path.exists(ud.localpath):
116 # file exists, but we didnt complete it.. trying again..
117 fetchcmd += d.expand(" -c -P ${DL_DIR} '%s'" % uri)
118 else:
119 fetchcmd += d.expand(" -P ${DL_DIR} '%s'" % uri)
120
121 self._runwget(ud, d, fetchcmd, False)
122
123 # Sanity check since wget can pretend it succeed when it didn't
124 # Also, this used to happen if sourceforge sent us to the mirror page
125 if not os.path.exists(ud.localpath):
126 raise FetchError("The fetch command returned success for url %s but %s doesn't exist?!" % (uri, ud.localpath), uri)
127
128 if os.path.getsize(ud.localpath) == 0:
129 os.remove(ud.localpath)
130 raise FetchError("The fetch of %s resulted in a zero size file?! Deleting and failing since this isn't right." % (uri), uri)
131
132 return True
133
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600134 def checkstatus(self, fetch, ud, d, try_again=True):
135 import urllib.request, urllib.error, urllib.parse, socket, http.client
136 from urllib.response import addinfourl
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500137 from bb.fetch2 import FetchConnectionCache
138
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600139 class HTTPConnectionCache(http.client.HTTPConnection):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500140 if fetch.connection_cache:
141 def connect(self):
142 """Connect to the host and port specified in __init__."""
143
144 sock = fetch.connection_cache.get_connection(self.host, self.port)
145 if sock:
146 self.sock = sock
147 else:
148 self.sock = socket.create_connection((self.host, self.port),
149 self.timeout, self.source_address)
150 fetch.connection_cache.add_connection(self.host, self.port, self.sock)
151
152 if self._tunnel_host:
153 self._tunnel()
154
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600155 class CacheHTTPHandler(urllib.request.HTTPHandler):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500156 def http_open(self, req):
157 return self.do_open(HTTPConnectionCache, req)
158
159 def do_open(self, http_class, req):
160 """Return an addinfourl object for the request, using http_class.
161
162 http_class must implement the HTTPConnection API from httplib.
163 The addinfourl return value is a file-like object. It also
164 has methods and attributes including:
165 - info(): return a mimetools.Message object for the headers
166 - geturl(): return the original request URL
167 - code: HTTP status code
168 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600169 host = req.host
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500170 if not host:
171 raise urlllib2.URLError('no host given')
172
173 h = http_class(host, timeout=req.timeout) # will parse host:port
174 h.set_debuglevel(self._debuglevel)
175
176 headers = dict(req.unredirected_hdrs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600177 headers.update(dict((k, v) for k, v in list(req.headers.items())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500178 if k not in headers))
179
180 # We want to make an HTTP/1.1 request, but the addinfourl
181 # class isn't prepared to deal with a persistent connection.
182 # It will try to read all remaining data from the socket,
183 # which will block while the server waits for the next request.
184 # So make sure the connection gets closed after the (only)
185 # request.
186
187 # Don't close connection when connection_cache is enabled,
188 if fetch.connection_cache is None:
189 headers["Connection"] = "close"
190 else:
191 headers["Connection"] = "Keep-Alive" # Works for HTTP/1.0
192
193 headers = dict(
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600194 (name.title(), val) for name, val in list(headers.items()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500195
196 if req._tunnel_host:
197 tunnel_headers = {}
198 proxy_auth_hdr = "Proxy-Authorization"
199 if proxy_auth_hdr in headers:
200 tunnel_headers[proxy_auth_hdr] = headers[proxy_auth_hdr]
201 # Proxy-Authorization should not be sent to origin
202 # server.
203 del headers[proxy_auth_hdr]
204 h.set_tunnel(req._tunnel_host, headers=tunnel_headers)
205
206 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600207 h.request(req.get_method(), req.selector, req.data, headers)
208 except socket.error as err: # XXX what error?
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500209 # Don't close connection when cache is enabled.
210 if fetch.connection_cache is None:
211 h.close()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600212 raise urllib.error.URLError(err)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500213 else:
214 try:
215 r = h.getresponse(buffering=True)
216 except TypeError: # buffering kw not supported
217 r = h.getresponse()
218
219 # Pick apart the HTTPResponse object to get the addinfourl
220 # object initialized properly.
221
222 # Wrap the HTTPResponse object in socket's file object adapter
223 # for Windows. That adapter calls recv(), so delegate recv()
224 # to read(). This weird wrapping allows the returned object to
225 # have readline() and readlines() methods.
226
227 # XXX It might be better to extract the read buffering code
228 # out of socket._fileobject() and into a base class.
229 r.recv = r.read
230
231 # no data, just have to read
232 r.read()
233 class fp_dummy(object):
234 def read(self):
235 return ""
236 def readline(self):
237 return ""
238 def close(self):
239 pass
240
241 resp = addinfourl(fp_dummy(), r.msg, req.get_full_url())
242 resp.code = r.status
243 resp.msg = r.reason
244
245 # Close connection when server request it.
246 if fetch.connection_cache is not None:
247 if 'Connection' in r.msg and r.msg['Connection'] == 'close':
248 fetch.connection_cache.remove_connection(h.host, h.port)
249
250 return resp
251
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600252 class HTTPMethodFallback(urllib.request.BaseHandler):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500253 """
254 Fallback to GET if HEAD is not allowed (405 HTTP error)
255 """
256 def http_error_405(self, req, fp, code, msg, headers):
257 fp.read()
258 fp.close()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500259
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600260 newheaders = dict((k,v) for k,v in list(req.headers.items())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500261 if k.lower() not in ("content-length", "content-type"))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600262 return self.parent.open(urllib.request.Request(req.get_full_url(),
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500263 headers=newheaders,
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600264 origin_req_host=req.origin_req_host,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500265 unverifiable=True))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500266
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500267 """
268 Some servers (e.g. GitHub archives, hosted on Amazon S3) return 403
269 Forbidden when they actually mean 405 Method Not Allowed.
270 """
271 http_error_403 = http_error_405
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500272
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500273 """
274 Some servers (e.g. FusionForge) returns 406 Not Acceptable when they
275 actually mean 405 Method Not Allowed.
276 """
277 http_error_406 = http_error_405
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500278
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600279 class FixedHTTPRedirectHandler(urllib.request.HTTPRedirectHandler):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500280 """
281 urllib2.HTTPRedirectHandler resets the method to GET on redirect,
282 when we want to follow redirects using the original method.
283 """
284 def redirect_request(self, req, fp, code, msg, headers, newurl):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600285 newreq = urllib.request.HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, headers, newurl)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500286 newreq.get_method = lambda: req.get_method()
287 return newreq
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500288 exported_proxies = export_proxies(d)
289
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500290 handlers = [FixedHTTPRedirectHandler, HTTPMethodFallback]
291 if export_proxies:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600292 handlers.append(urllib.request.ProxyHandler())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500293 handlers.append(CacheHTTPHandler())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500294 # XXX: Since Python 2.7.9 ssl cert validation is enabled by default
295 # see PEP-0476, this causes verification errors on some https servers
296 # so disable by default.
297 import ssl
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500298 if hasattr(ssl, '_create_unverified_context'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600299 handlers.append(urllib.request.HTTPSHandler(context=ssl._create_unverified_context()))
300 opener = urllib.request.build_opener(*handlers)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500301
302 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500303 uri = ud.url.split(";")[0]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600304 r = urllib.request.Request(uri)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500305 r.get_method = lambda: "HEAD"
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600306
307 if ud.user:
308 import base64
309 encodeuser = base64.b64encode(ud.user.encode('utf-8')).decode("utf-8")
310 authheader = "Basic %s" % encodeuser
311 r.add_header("Authorization", authheader)
312
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500313 opener.open(r)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600314 except urllib.error.URLError as e:
315 if try_again:
316 logger.debug(2, "checkstatus: trying again")
317 return self.checkstatus(fetch, ud, d, False)
318 else:
319 # debug for now to avoid spamming the logs in e.g. remote sstate searches
320 logger.debug(2, "checkstatus() urlopen failed: %s" % e)
321 return False
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500322 return True
323
324 def _parse_path(self, regex, s):
325 """
326 Find and group name, version and archive type in the given string s
327 """
328
329 m = regex.search(s)
330 if m:
331 pname = ''
332 pver = ''
333 ptype = ''
334
335 mdict = m.groupdict()
336 if 'name' in mdict.keys():
337 pname = mdict['name']
338 if 'pver' in mdict.keys():
339 pver = mdict['pver']
340 if 'type' in mdict.keys():
341 ptype = mdict['type']
342
343 bb.debug(3, "_parse_path: %s, %s, %s" % (pname, pver, ptype))
344
345 return (pname, pver, ptype)
346
347 return None
348
349 def _modelate_version(self, version):
350 if version[0] in ['.', '-']:
351 if version[1].isdigit():
352 version = version[1] + version[0] + version[2:len(version)]
353 else:
354 version = version[1:len(version)]
355
356 version = re.sub('-', '.', version)
357 version = re.sub('_', '.', version)
358 version = re.sub('(rc)+', '.1000.', version)
359 version = re.sub('(beta)+', '.100.', version)
360 version = re.sub('(alpha)+', '.10.', version)
361 if version[0] == 'v':
362 version = version[1:len(version)]
363 return version
364
365 def _vercmp(self, old, new):
366 """
367 Check whether 'new' is newer than 'old' version. We use existing vercmp() for the
368 purpose. PE is cleared in comparison as it's not for build, and PR is cleared too
369 for simplicity as it's somehow difficult to get from various upstream format
370 """
371
372 (oldpn, oldpv, oldsuffix) = old
373 (newpn, newpv, newsuffix) = new
374
375 """
376 Check for a new suffix type that we have never heard of before
377 """
378 if (newsuffix):
379 m = self.suffix_regex_comp.search(newsuffix)
380 if not m:
381 bb.warn("%s has a possible unknown suffix: %s" % (newpn, newsuffix))
382 return False
383
384 """
385 Not our package so ignore it
386 """
387 if oldpn != newpn:
388 return False
389
390 oldpv = self._modelate_version(oldpv)
391 newpv = self._modelate_version(newpv)
392
393 return bb.utils.vercmp(("0", oldpv, ""), ("0", newpv, ""))
394
395 def _fetch_index(self, uri, ud, d):
396 """
397 Run fetch checkstatus to get directory information
398 """
399 f = tempfile.NamedTemporaryFile()
400
401 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"
402 fetchcmd = self.basecmd
403 fetchcmd += " -O " + f.name + " --user-agent='" + agent + "' '" + uri + "'"
404 try:
405 self._runwget(ud, d, fetchcmd, True)
406 fetchresult = f.read()
407 except bb.fetch2.BBFetchException:
408 fetchresult = ""
409
410 f.close()
411 return fetchresult
412
413 def _check_latest_version(self, url, package, package_regex, current_version, ud, d):
414 """
415 Return the latest version of a package inside a given directory path
416 If error or no version, return ""
417 """
418 valid = 0
419 version = ['', '', '']
420
421 bb.debug(3, "VersionURL: %s" % (url))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500422 soup = BeautifulSoup(self._fetch_index(url, ud, d), "html.parser", parse_only=SoupStrainer("a"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500423 if not soup:
424 bb.debug(3, "*** %s NO SOUP" % (url))
425 return ""
426
427 for line in soup.find_all('a', href=True):
428 bb.debug(3, "line['href'] = '%s'" % (line['href']))
429 bb.debug(3, "line = '%s'" % (str(line)))
430
431 newver = self._parse_path(package_regex, line['href'])
432 if not newver:
433 newver = self._parse_path(package_regex, str(line))
434
435 if newver:
436 bb.debug(3, "Upstream version found: %s" % newver[1])
437 if valid == 0:
438 version = newver
439 valid = 1
440 elif self._vercmp(version, newver) < 0:
441 version = newver
442
443 pupver = re.sub('_', '.', version[1])
444
445 bb.debug(3, "*** %s -> UpstreamVersion = %s (CurrentVersion = %s)" %
446 (package, pupver or "N/A", current_version[1]))
447
448 if valid:
449 return pupver
450
451 return ""
452
453 def _check_latest_version_by_dir(self, dirver, package, package_regex,
454 current_version, ud, d):
455 """
456 Scan every directory in order to get upstream version.
457 """
458 version_dir = ['', '', '']
459 version = ['', '', '']
460
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500461 dirver_regex = re.compile("(?P<pfx>\D*)(?P<ver>(\d+[\.\-_])+(\d+))")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500462 s = dirver_regex.search(dirver)
463 if s:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500464 version_dir[1] = s.group('ver')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500465 else:
466 version_dir[1] = dirver
467
468 dirs_uri = bb.fetch.encodeurl([ud.type, ud.host,
469 ud.path.split(dirver)[0], ud.user, ud.pswd, {}])
470 bb.debug(3, "DirURL: %s, %s" % (dirs_uri, package))
471
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500472 soup = BeautifulSoup(self._fetch_index(dirs_uri, ud, d), "html.parser", parse_only=SoupStrainer("a"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500473 if not soup:
474 return version[1]
475
476 for line in soup.find_all('a', href=True):
477 s = dirver_regex.search(line['href'].strip("/"))
478 if s:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500479 sver = s.group('ver')
480
481 # When prefix is part of the version directory it need to
482 # ensure that only version directory is used so remove previous
483 # directories if exists.
484 #
485 # Example: pfx = '/dir1/dir2/v' and version = '2.5' the expected
486 # result is v2.5.
487 spfx = s.group('pfx').split('/')[-1]
488
489 version_dir_new = ['', sver, '']
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500490 if self._vercmp(version_dir, version_dir_new) <= 0:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500491 dirver_new = spfx + sver
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500492 path = ud.path.replace(dirver, dirver_new, True) \
493 .split(package)[0]
494 uri = bb.fetch.encodeurl([ud.type, ud.host, path,
495 ud.user, ud.pswd, {}])
496
497 pupver = self._check_latest_version(uri,
498 package, package_regex, current_version, ud, d)
499 if pupver:
500 version[1] = pupver
501
502 version_dir = version_dir_new
503
504 return version[1]
505
506 def _init_regexes(self, package, ud, d):
507 """
508 Match as many patterns as possible such as:
509 gnome-common-2.20.0.tar.gz (most common format)
510 gtk+-2.90.1.tar.gz
511 xf86-input-synaptics-12.6.9.tar.gz
512 dri2proto-2.3.tar.gz
513 blktool_4.orig.tar.gz
514 libid3tag-0.15.1b.tar.gz
515 unzip552.tar.gz
516 icu4c-3_6-src.tgz
517 genext2fs_1.3.orig.tar.gz
518 gst-fluendo-mp3
519 """
520 # match most patterns which uses "-" as separator to version digits
521 pn_prefix1 = "[a-zA-Z][a-zA-Z0-9]*([-_][a-zA-Z]\w+)*\+?[-_]"
522 # a loose pattern such as for unzip552.tar.gz
523 pn_prefix2 = "[a-zA-Z]+"
524 # a loose pattern such as for 80325-quicky-0.4.tar.gz
525 pn_prefix3 = "[0-9]+[-]?[a-zA-Z]+"
526 # Save the Package Name (pn) Regex for use later
527 pn_regex = "(%s|%s|%s)" % (pn_prefix1, pn_prefix2, pn_prefix3)
528
529 # match version
530 pver_regex = "(([A-Z]*\d+[a-zA-Z]*[\.\-_]*)+)"
531
532 # match arch
533 parch_regex = "-source|_all_"
534
535 # src.rpm extension was added only for rpm package. Can be removed if the rpm
536 # packaged will always be considered as having to be manually upgraded
537 psuffix_regex = "(tar\.gz|tgz|tar\.bz2|zip|xz|rpm|bz2|orig\.tar\.gz|tar\.xz|src\.tar\.gz|src\.tgz|svnr\d+\.tar\.bz2|stable\.tar\.gz|src\.rpm)"
538
539 # match name, version and archive type of a package
540 package_regex_comp = re.compile("(?P<name>%s?\.?v?)(?P<pver>%s)(?P<arch>%s)?[\.-](?P<type>%s$)"
541 % (pn_regex, pver_regex, parch_regex, psuffix_regex))
542 self.suffix_regex_comp = re.compile(psuffix_regex)
543
544 # compile regex, can be specific by package or generic regex
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500545 pn_regex = d.getVar('UPSTREAM_CHECK_REGEX', True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500546 if pn_regex:
547 package_custom_regex_comp = re.compile(pn_regex)
548 else:
549 version = self._parse_path(package_regex_comp, package)
550 if version:
551 package_custom_regex_comp = re.compile(
552 "(?P<name>%s)(?P<pver>%s)(?P<arch>%s)?[\.-](?P<type>%s)" %
553 (re.escape(version[0]), pver_regex, parch_regex, psuffix_regex))
554 else:
555 package_custom_regex_comp = None
556
557 return package_custom_regex_comp
558
559 def latest_versionstring(self, ud, d):
560 """
561 Manipulate the URL and try to obtain the latest package version
562
563 sanity check to ensure same name and type.
564 """
565 package = ud.path.split("/")[-1]
566 current_version = ['', d.getVar('PV', True), '']
567
568 """possible to have no version in pkg name, such as spectrum-fw"""
569 if not re.search("\d+", package):
570 current_version[1] = re.sub('_', '.', current_version[1])
571 current_version[1] = re.sub('-', '.', current_version[1])
572 return (current_version[1], '')
573
574 package_regex = self._init_regexes(package, ud, d)
575 if package_regex is None:
576 bb.warn("latest_versionstring: package %s don't match pattern" % (package))
577 return ('', '')
578 bb.debug(3, "latest_versionstring, regex: %s" % (package_regex.pattern))
579
580 uri = ""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500581 regex_uri = d.getVar("UPSTREAM_CHECK_URI", True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500582 if not regex_uri:
583 path = ud.path.split(package)[0]
584
585 # search for version matches on folders inside the path, like:
586 # "5.7" in http://download.gnome.org/sources/${PN}/5.7/${PN}-${PV}.tar.gz
587 dirver_regex = re.compile("(?P<dirver>[^/]*(\d+\.)*\d+([-_]r\d+)*)/")
588 m = dirver_regex.search(path)
589 if m:
590 pn = d.getVar('PN', True)
591 dirver = m.group('dirver')
592
593 dirver_pn_regex = re.compile("%s\d?" % (re.escape(pn)))
594 if not dirver_pn_regex.search(dirver):
595 return (self._check_latest_version_by_dir(dirver,
596 package, package_regex, current_version, ud, d), '')
597
598 uri = bb.fetch.encodeurl([ud.type, ud.host, path, ud.user, ud.pswd, {}])
599 else:
600 uri = regex_uri
601
602 return (self._check_latest_version(uri, package, package_regex,
603 current_version, ud, d), '')