blob: ecb946aa81f036506b0c97dc2d12bc9ef253db11 [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
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600111 if ud.user:
112 up = ud.user.split(":")
113 fetchcmd += " --user=%s --password=%s --auth-no-challenge" % (up[0],up[1])
114
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500115 uri = ud.url.split(";")[0]
116 if os.path.exists(ud.localpath):
117 # file exists, but we didnt complete it.. trying again..
118 fetchcmd += d.expand(" -c -P ${DL_DIR} '%s'" % uri)
119 else:
120 fetchcmd += d.expand(" -P ${DL_DIR} '%s'" % uri)
121
122 self._runwget(ud, d, fetchcmd, False)
123
124 # Sanity check since wget can pretend it succeed when it didn't
125 # Also, this used to happen if sourceforge sent us to the mirror page
126 if not os.path.exists(ud.localpath):
127 raise FetchError("The fetch command returned success for url %s but %s doesn't exist?!" % (uri, ud.localpath), uri)
128
129 if os.path.getsize(ud.localpath) == 0:
130 os.remove(ud.localpath)
131 raise FetchError("The fetch of %s resulted in a zero size file?! Deleting and failing since this isn't right." % (uri), uri)
132
133 return True
134
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600135 def checkstatus(self, fetch, ud, d, try_again=True):
136 import urllib.request, urllib.error, urllib.parse, socket, http.client
137 from urllib.response import addinfourl
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500138 from bb.fetch2 import FetchConnectionCache
139
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600140 class HTTPConnectionCache(http.client.HTTPConnection):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500141 if fetch.connection_cache:
142 def connect(self):
143 """Connect to the host and port specified in __init__."""
144
145 sock = fetch.connection_cache.get_connection(self.host, self.port)
146 if sock:
147 self.sock = sock
148 else:
149 self.sock = socket.create_connection((self.host, self.port),
150 self.timeout, self.source_address)
151 fetch.connection_cache.add_connection(self.host, self.port, self.sock)
152
153 if self._tunnel_host:
154 self._tunnel()
155
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600156 class CacheHTTPHandler(urllib.request.HTTPHandler):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500157 def http_open(self, req):
158 return self.do_open(HTTPConnectionCache, req)
159
160 def do_open(self, http_class, req):
161 """Return an addinfourl object for the request, using http_class.
162
163 http_class must implement the HTTPConnection API from httplib.
164 The addinfourl return value is a file-like object. It also
165 has methods and attributes including:
166 - info(): return a mimetools.Message object for the headers
167 - geturl(): return the original request URL
168 - code: HTTP status code
169 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600170 host = req.host
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500171 if not host:
172 raise urlllib2.URLError('no host given')
173
174 h = http_class(host, timeout=req.timeout) # will parse host:port
175 h.set_debuglevel(self._debuglevel)
176
177 headers = dict(req.unredirected_hdrs)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600178 headers.update(dict((k, v) for k, v in list(req.headers.items())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500179 if k not in headers))
180
181 # We want to make an HTTP/1.1 request, but the addinfourl
182 # class isn't prepared to deal with a persistent connection.
183 # It will try to read all remaining data from the socket,
184 # which will block while the server waits for the next request.
185 # So make sure the connection gets closed after the (only)
186 # request.
187
188 # Don't close connection when connection_cache is enabled,
189 if fetch.connection_cache is None:
190 headers["Connection"] = "close"
191 else:
192 headers["Connection"] = "Keep-Alive" # Works for HTTP/1.0
193
194 headers = dict(
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600195 (name.title(), val) for name, val in list(headers.items()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500196
197 if req._tunnel_host:
198 tunnel_headers = {}
199 proxy_auth_hdr = "Proxy-Authorization"
200 if proxy_auth_hdr in headers:
201 tunnel_headers[proxy_auth_hdr] = headers[proxy_auth_hdr]
202 # Proxy-Authorization should not be sent to origin
203 # server.
204 del headers[proxy_auth_hdr]
205 h.set_tunnel(req._tunnel_host, headers=tunnel_headers)
206
207 try:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600208 h.request(req.get_method(), req.selector, req.data, headers)
209 except socket.error as err: # XXX what error?
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500210 # Don't close connection when cache is enabled.
211 if fetch.connection_cache is None:
212 h.close()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600213 raise urllib.error.URLError(err)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500214 else:
215 try:
216 r = h.getresponse(buffering=True)
217 except TypeError: # buffering kw not supported
218 r = h.getresponse()
219
220 # Pick apart the HTTPResponse object to get the addinfourl
221 # object initialized properly.
222
223 # Wrap the HTTPResponse object in socket's file object adapter
224 # for Windows. That adapter calls recv(), so delegate recv()
225 # to read(). This weird wrapping allows the returned object to
226 # have readline() and readlines() methods.
227
228 # XXX It might be better to extract the read buffering code
229 # out of socket._fileobject() and into a base class.
230 r.recv = r.read
231
232 # no data, just have to read
233 r.read()
234 class fp_dummy(object):
235 def read(self):
236 return ""
237 def readline(self):
238 return ""
239 def close(self):
240 pass
241
242 resp = addinfourl(fp_dummy(), r.msg, req.get_full_url())
243 resp.code = r.status
244 resp.msg = r.reason
245
246 # Close connection when server request it.
247 if fetch.connection_cache is not None:
248 if 'Connection' in r.msg and r.msg['Connection'] == 'close':
249 fetch.connection_cache.remove_connection(h.host, h.port)
250
251 return resp
252
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600253 class HTTPMethodFallback(urllib.request.BaseHandler):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500254 """
255 Fallback to GET if HEAD is not allowed (405 HTTP error)
256 """
257 def http_error_405(self, req, fp, code, msg, headers):
258 fp.read()
259 fp.close()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500260
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600261 newheaders = dict((k,v) for k,v in list(req.headers.items())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500262 if k.lower() not in ("content-length", "content-type"))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600263 return self.parent.open(urllib.request.Request(req.get_full_url(),
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500264 headers=newheaders,
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600265 origin_req_host=req.origin_req_host,
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500266 unverifiable=True))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500267
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500268 """
269 Some servers (e.g. GitHub archives, hosted on Amazon S3) return 403
270 Forbidden when they actually mean 405 Method Not Allowed.
271 """
272 http_error_403 = http_error_405
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500273
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500274 """
275 Some servers (e.g. FusionForge) returns 406 Not Acceptable when they
276 actually mean 405 Method Not Allowed.
277 """
278 http_error_406 = http_error_405
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500279
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600280 class FixedHTTPRedirectHandler(urllib.request.HTTPRedirectHandler):
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500281 """
282 urllib2.HTTPRedirectHandler resets the method to GET on redirect,
283 when we want to follow redirects using the original method.
284 """
285 def redirect_request(self, req, fp, code, msg, headers, newurl):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600286 newreq = urllib.request.HTTPRedirectHandler.redirect_request(self, req, fp, code, msg, headers, newurl)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500287 newreq.get_method = lambda: req.get_method()
288 return newreq
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500289 exported_proxies = export_proxies(d)
290
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500291 handlers = [FixedHTTPRedirectHandler, HTTPMethodFallback]
292 if export_proxies:
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600293 handlers.append(urllib.request.ProxyHandler())
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500294 handlers.append(CacheHTTPHandler())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500295 # XXX: Since Python 2.7.9 ssl cert validation is enabled by default
296 # see PEP-0476, this causes verification errors on some https servers
297 # so disable by default.
298 import ssl
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500299 if hasattr(ssl, '_create_unverified_context'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600300 handlers.append(urllib.request.HTTPSHandler(context=ssl._create_unverified_context()))
301 opener = urllib.request.build_opener(*handlers)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500302
303 try:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500304 uri = ud.url.split(";")[0]
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600305 r = urllib.request.Request(uri)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500306 r.get_method = lambda: "HEAD"
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600307
308 if ud.user:
309 import base64
310 encodeuser = base64.b64encode(ud.user.encode('utf-8')).decode("utf-8")
311 authheader = "Basic %s" % encodeuser
312 r.add_header("Authorization", authheader)
313
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500314 opener.open(r)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600315 except urllib.error.URLError as e:
316 if try_again:
317 logger.debug(2, "checkstatus: trying again")
318 return self.checkstatus(fetch, ud, d, False)
319 else:
320 # debug for now to avoid spamming the logs in e.g. remote sstate searches
321 logger.debug(2, "checkstatus() urlopen failed: %s" % e)
322 return False
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500323 return True
324
325 def _parse_path(self, regex, s):
326 """
327 Find and group name, version and archive type in the given string s
328 """
329
330 m = regex.search(s)
331 if m:
332 pname = ''
333 pver = ''
334 ptype = ''
335
336 mdict = m.groupdict()
337 if 'name' in mdict.keys():
338 pname = mdict['name']
339 if 'pver' in mdict.keys():
340 pver = mdict['pver']
341 if 'type' in mdict.keys():
342 ptype = mdict['type']
343
344 bb.debug(3, "_parse_path: %s, %s, %s" % (pname, pver, ptype))
345
346 return (pname, pver, ptype)
347
348 return None
349
350 def _modelate_version(self, version):
351 if version[0] in ['.', '-']:
352 if version[1].isdigit():
353 version = version[1] + version[0] + version[2:len(version)]
354 else:
355 version = version[1:len(version)]
356
357 version = re.sub('-', '.', version)
358 version = re.sub('_', '.', version)
359 version = re.sub('(rc)+', '.1000.', version)
360 version = re.sub('(beta)+', '.100.', version)
361 version = re.sub('(alpha)+', '.10.', version)
362 if version[0] == 'v':
363 version = version[1:len(version)]
364 return version
365
366 def _vercmp(self, old, new):
367 """
368 Check whether 'new' is newer than 'old' version. We use existing vercmp() for the
369 purpose. PE is cleared in comparison as it's not for build, and PR is cleared too
370 for simplicity as it's somehow difficult to get from various upstream format
371 """
372
373 (oldpn, oldpv, oldsuffix) = old
374 (newpn, newpv, newsuffix) = new
375
376 """
377 Check for a new suffix type that we have never heard of before
378 """
379 if (newsuffix):
380 m = self.suffix_regex_comp.search(newsuffix)
381 if not m:
382 bb.warn("%s has a possible unknown suffix: %s" % (newpn, newsuffix))
383 return False
384
385 """
386 Not our package so ignore it
387 """
388 if oldpn != newpn:
389 return False
390
391 oldpv = self._modelate_version(oldpv)
392 newpv = self._modelate_version(newpv)
393
394 return bb.utils.vercmp(("0", oldpv, ""), ("0", newpv, ""))
395
396 def _fetch_index(self, uri, ud, d):
397 """
398 Run fetch checkstatus to get directory information
399 """
400 f = tempfile.NamedTemporaryFile()
401
402 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"
403 fetchcmd = self.basecmd
404 fetchcmd += " -O " + f.name + " --user-agent='" + agent + "' '" + uri + "'"
405 try:
406 self._runwget(ud, d, fetchcmd, True)
407 fetchresult = f.read()
408 except bb.fetch2.BBFetchException:
409 fetchresult = ""
410
411 f.close()
412 return fetchresult
413
414 def _check_latest_version(self, url, package, package_regex, current_version, ud, d):
415 """
416 Return the latest version of a package inside a given directory path
417 If error or no version, return ""
418 """
419 valid = 0
420 version = ['', '', '']
421
422 bb.debug(3, "VersionURL: %s" % (url))
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500423 soup = BeautifulSoup(self._fetch_index(url, ud, d), "html.parser", parse_only=SoupStrainer("a"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500424 if not soup:
425 bb.debug(3, "*** %s NO SOUP" % (url))
426 return ""
427
428 for line in soup.find_all('a', href=True):
429 bb.debug(3, "line['href'] = '%s'" % (line['href']))
430 bb.debug(3, "line = '%s'" % (str(line)))
431
432 newver = self._parse_path(package_regex, line['href'])
433 if not newver:
434 newver = self._parse_path(package_regex, str(line))
435
436 if newver:
437 bb.debug(3, "Upstream version found: %s" % newver[1])
438 if valid == 0:
439 version = newver
440 valid = 1
441 elif self._vercmp(version, newver) < 0:
442 version = newver
443
444 pupver = re.sub('_', '.', version[1])
445
446 bb.debug(3, "*** %s -> UpstreamVersion = %s (CurrentVersion = %s)" %
447 (package, pupver or "N/A", current_version[1]))
448
449 if valid:
450 return pupver
451
452 return ""
453
454 def _check_latest_version_by_dir(self, dirver, package, package_regex,
455 current_version, ud, d):
456 """
457 Scan every directory in order to get upstream version.
458 """
459 version_dir = ['', '', '']
460 version = ['', '', '']
461
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500462 dirver_regex = re.compile("(?P<pfx>\D*)(?P<ver>(\d+[\.\-_])+(\d+))")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500463 s = dirver_regex.search(dirver)
464 if s:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500465 version_dir[1] = s.group('ver')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500466 else:
467 version_dir[1] = dirver
468
469 dirs_uri = bb.fetch.encodeurl([ud.type, ud.host,
470 ud.path.split(dirver)[0], ud.user, ud.pswd, {}])
471 bb.debug(3, "DirURL: %s, %s" % (dirs_uri, package))
472
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500473 soup = BeautifulSoup(self._fetch_index(dirs_uri, ud, d), "html.parser", parse_only=SoupStrainer("a"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500474 if not soup:
475 return version[1]
476
477 for line in soup.find_all('a', href=True):
478 s = dirver_regex.search(line['href'].strip("/"))
479 if s:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500480 sver = s.group('ver')
481
482 # When prefix is part of the version directory it need to
483 # ensure that only version directory is used so remove previous
484 # directories if exists.
485 #
486 # Example: pfx = '/dir1/dir2/v' and version = '2.5' the expected
487 # result is v2.5.
488 spfx = s.group('pfx').split('/')[-1]
489
490 version_dir_new = ['', sver, '']
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500491 if self._vercmp(version_dir, version_dir_new) <= 0:
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500492 dirver_new = spfx + sver
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500493 path = ud.path.replace(dirver, dirver_new, True) \
494 .split(package)[0]
495 uri = bb.fetch.encodeurl([ud.type, ud.host, path,
496 ud.user, ud.pswd, {}])
497
498 pupver = self._check_latest_version(uri,
499 package, package_regex, current_version, ud, d)
500 if pupver:
501 version[1] = pupver
502
503 version_dir = version_dir_new
504
505 return version[1]
506
507 def _init_regexes(self, package, ud, d):
508 """
509 Match as many patterns as possible such as:
510 gnome-common-2.20.0.tar.gz (most common format)
511 gtk+-2.90.1.tar.gz
512 xf86-input-synaptics-12.6.9.tar.gz
513 dri2proto-2.3.tar.gz
514 blktool_4.orig.tar.gz
515 libid3tag-0.15.1b.tar.gz
516 unzip552.tar.gz
517 icu4c-3_6-src.tgz
518 genext2fs_1.3.orig.tar.gz
519 gst-fluendo-mp3
520 """
521 # match most patterns which uses "-" as separator to version digits
522 pn_prefix1 = "[a-zA-Z][a-zA-Z0-9]*([-_][a-zA-Z]\w+)*\+?[-_]"
523 # a loose pattern such as for unzip552.tar.gz
524 pn_prefix2 = "[a-zA-Z]+"
525 # a loose pattern such as for 80325-quicky-0.4.tar.gz
526 pn_prefix3 = "[0-9]+[-]?[a-zA-Z]+"
527 # Save the Package Name (pn) Regex for use later
528 pn_regex = "(%s|%s|%s)" % (pn_prefix1, pn_prefix2, pn_prefix3)
529
530 # match version
531 pver_regex = "(([A-Z]*\d+[a-zA-Z]*[\.\-_]*)+)"
532
533 # match arch
534 parch_regex = "-source|_all_"
535
536 # src.rpm extension was added only for rpm package. Can be removed if the rpm
537 # packaged will always be considered as having to be manually upgraded
538 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)"
539
540 # match name, version and archive type of a package
541 package_regex_comp = re.compile("(?P<name>%s?\.?v?)(?P<pver>%s)(?P<arch>%s)?[\.-](?P<type>%s$)"
542 % (pn_regex, pver_regex, parch_regex, psuffix_regex))
543 self.suffix_regex_comp = re.compile(psuffix_regex)
544
545 # compile regex, can be specific by package or generic regex
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500546 pn_regex = d.getVar('UPSTREAM_CHECK_REGEX', True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500547 if pn_regex:
548 package_custom_regex_comp = re.compile(pn_regex)
549 else:
550 version = self._parse_path(package_regex_comp, package)
551 if version:
552 package_custom_regex_comp = re.compile(
553 "(?P<name>%s)(?P<pver>%s)(?P<arch>%s)?[\.-](?P<type>%s)" %
554 (re.escape(version[0]), pver_regex, parch_regex, psuffix_regex))
555 else:
556 package_custom_regex_comp = None
557
558 return package_custom_regex_comp
559
560 def latest_versionstring(self, ud, d):
561 """
562 Manipulate the URL and try to obtain the latest package version
563
564 sanity check to ensure same name and type.
565 """
566 package = ud.path.split("/")[-1]
567 current_version = ['', d.getVar('PV', True), '']
568
569 """possible to have no version in pkg name, such as spectrum-fw"""
570 if not re.search("\d+", package):
571 current_version[1] = re.sub('_', '.', current_version[1])
572 current_version[1] = re.sub('-', '.', current_version[1])
573 return (current_version[1], '')
574
575 package_regex = self._init_regexes(package, ud, d)
576 if package_regex is None:
577 bb.warn("latest_versionstring: package %s don't match pattern" % (package))
578 return ('', '')
579 bb.debug(3, "latest_versionstring, regex: %s" % (package_regex.pattern))
580
581 uri = ""
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500582 regex_uri = d.getVar("UPSTREAM_CHECK_URI", True)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500583 if not regex_uri:
584 path = ud.path.split(package)[0]
585
586 # search for version matches on folders inside the path, like:
587 # "5.7" in http://download.gnome.org/sources/${PN}/5.7/${PN}-${PV}.tar.gz
588 dirver_regex = re.compile("(?P<dirver>[^/]*(\d+\.)*\d+([-_]r\d+)*)/")
589 m = dirver_regex.search(path)
590 if m:
591 pn = d.getVar('PN', True)
592 dirver = m.group('dirver')
593
594 dirver_pn_regex = re.compile("%s\d?" % (re.escape(pn)))
595 if not dirver_pn_regex.search(dirver):
596 return (self._check_latest_version_by_dir(dirver,
597 package, package_regex, current_version, ud, d), '')
598
599 uri = bb.fetch.encodeurl([ud.type, ud.host, path, ud.user, ud.pswd, {}])
600 else:
601 uri = regex_uri
602
603 return (self._check_latest_version(uri, package, package_regex,
604 current_version, ud, d), '')