source: rtems-source-builder/source-builder/sb/download.py @ 76188ee4

4.11
Last change on this file since 76188ee4 was 76188ee4, checked in by Chris Johns <chrisj@…>, on 02/28/16 at 23:41:17

sb: Remove http query flags from the file name if present.

The query flags such as '?h=4.11' to select a branch cannot appear
in the file name on Windows. This patch removes the query part from
the file name.

Update #2616.

  • Property mode set to 100644
File size: 20.7 KB
RevLine 
[649a64c]1#
2# RTEMS Tools Project (http://www.rtems.org/)
[76188ee4]3# Copyright 2010-2016 Chris Johns (chrisj@rtems.org)
[649a64c]4# All rights reserved.
5#
6# This file is part of the RTEMS Tools package in 'rtems-tools'.
7#
8# Permission to use, copy, modify, and/or distribute this software for any
9# purpose with or without fee is hereby granted, provided that the above
10# copyright notice and this permission notice appear in all copies.
11#
12# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
20#
21# This code builds a package given a config file. It only builds to be
22# installed not to be package unless you run a packager around this.
23#
24
[a083b52]25import hashlib
[649a64c]26import os
27import stat
28import sys
29import urllib2
30import urlparse
31
[4ce931b]32import cvs
[649a64c]33import error
34import git
35import log
36import path
[d3629a9]37import sources
[b6d188b]38import version
[649a64c]39
[d755065]40def _do_download(opts):
41    download = True
42    if opts.dry_run():
43        download = False
44        wa = opts.with_arg('download')
45        if wa is not None:
46            if wa[0] == 'with_download' and wa[1] == 'yes':
47                download = True
48    return download
49
[c49e500]50def _humanize_bytes(bytes, precision = 1):
51    abbrevs = (
52        (1 << 50L, 'PB'),
53        (1 << 40L, 'TB'),
54        (1 << 30L, 'GB'),
55        (1 << 20L, 'MB'),
56        (1 << 10L, 'kB'),
57        (1, ' bytes')
58    )
59    if bytes == 1:
60        return '1 byte'
61    for factor, suffix in abbrevs:
62        if bytes >= factor:
63            break
64    return '%.*f%s' % (precision, float(bytes) / factor, suffix)
65
[a083b52]66def _hash_check(file_, absfile, macros, remove = True):
67    failed = False
[4934771]68    hash = sources.get_hash(file_.lower(), macros)
69    if hash is not None:
[a083b52]70        hash = hash.split()
71        if len(hash) != 2:
72            raise error.internal('invalid hash format: %s' % (file_))
[910081d]73        try:
74            hashlib_algorithms = hashlib.algorithms
75        except:
76            hashlib_algorithms = ['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']
77        if hash[0] not in hashlib_algorithms:
[a083b52]78            raise error.general('invalid hash algorithm for %s: %s' % (file_, hash[0]))
79        hasher = None
80        _in = None
81        try:
82            hasher = hashlib.new(hash[0])
[d4eb08f]83            _in = open(path.host(absfile), 'rb')
[a083b52]84            hasher.update(_in.read())
85        except IOError, err:
86            log.notice('hash: %s: read error: %s' % (file_, str(err)))
87            failed = True
88        except:
89            msg = 'hash: %s: error' % (file_)
90            log.stderr(msg)
91            log.notice(msg)
92            if _in is not None:
93                _in.close()
94            raise
95        if _in is not None:
96            _in.close()
97        log.output('checksums: %s: %s => %s' % (file_, hasher.hexdigest(), hash[1]))
98        if hasher.hexdigest() != hash[1]:
99            log.warning('checksum error: %s' % (file_))
100            failed = True
101        if failed and remove:
102            log.warning('removing: %s' % (file_))
103            if path.exists(absfile):
[d4eb08f]104                try:
105                    os.remove(path.host(absfile))
106                except IOError, err:
107                    raise error.general('hash: %s: remove: %s' % (absfile, str(err)))
108                except:
109                    raise error.general('hash: %s: remove error' % (file_))
[a083b52]110        if hasher is not None:
111            del hasher
112    else:
[b6d188b]113        if version.released():
114            raise error.general('%s: no hash found in released RSB' % (file_))
[a083b52]115        log.warning('%s: no hash found' % (file_))
116    return not failed
117
[d755065]118def _local_path(source, pathkey, config):
119    for p in config.define(pathkey).split(':'):
120        local = path.join(path.abspath(p), source['file'])
121        if source['local'] is None:
122            source['local_prefix'] = path.abspath(p)
123            source['local'] = local
124        if path.exists(local):
125            source['local_prefix'] = path.abspath(p)
126            source['local'] = local
127            _hash_check(source['file'], local, config.macros)
128            break
129
130def _http_parser(source, pathkey, config, opts):
131    #
132    # Hack for gitweb.cgi patch downloads. We rewrite the various fields.
133    #
134    if 'gitweb.cgi' in source['url']:
135        url = source['url']
136        if '?' not in url:
137            raise error.general('invalid gitweb.cgi request: %s' % (url))
138        req = url.split('?')[1]
139        if len(req) == 0:
140            raise error.general('invalid gitweb.cgi request: %s' % (url))
141        #
142        # The gitweb.cgi request should have:
143        #    p=<what>
144        #    a=patch
145        #    h=<hash>
146        # so extract the p and h parts to make the local name.
147        #
148        p = None
149        a = None
150        h = None
151        for r in req.split(';'):
152            if '=' not in r:
153                raise error.general('invalid gitweb.cgi path: %s' % (url))
154            rs = r.split('=')
155            if rs[0] == 'p':
156                p = rs[1].replace('.', '-')
157            elif rs[0] == 'a':
158                a = rs[1]
159            elif rs[0] == 'h':
160                h = rs[1]
161        if p is None or h is None:
162            raise error.general('gitweb.cgi path missing p or h: %s' % (url))
163        source['file'] = '%s-%s.patch' % (p, h)
164    #
[76188ee4]165    # Check the source file name for any extra request query data and remove if
166    # found. Some hosts do not like file names containing them.
167    #
168    if '?' in source['file']:
169        qmark = source['file'].find('?')
170        source['file'] = source['file'][:qmark]
171    #
[d755065]172    # Check local path
173    #
174    _local_path(source, pathkey, config)
[649a64c]175    #
176    # Is the file compressed ?
177    #
178    esl = source['ext'].split('.')
179    if esl[-1:][0] == 'gz':
[9a15c40]180        source['compressed-type'] = 'gzip'
[649a64c]181        source['compressed'] = '%{__gzip} -dc'
182    elif esl[-1:][0] == 'bz2':
[9a15c40]183        source['compressed-type'] = 'bzip2'
[649a64c]184        source['compressed'] = '%{__bzip2} -dc'
[29a300a]185    elif esl[-1:][0] == 'zip':
[9a15c40]186        source['compressed-type'] = 'zip'
[e7a6292]187        source['compressed'] = '%{__unzip} -u'
[649a64c]188    elif esl[-1:][0] == 'xz':
[9a15c40]189        source['compressed-type'] = 'xz'
[649a64c]190        source['compressed'] = '%{__xz} -dc'
191
[d755065]192def _patchworks_parser(source, pathkey, config, opts):
193    #
194    # Check local path
195    #
196    _local_path(source, pathkey, config)
[587aa5f]197    source['url'] = 'http%s' % (source['path'][2:])
198
[d755065]199def _git_parser(source, pathkey, config, opts):
200    #
201    # Check local path
202    #
203    _local_path(source, pathkey, config)
[649a64c]204    #
205    # Symlink.
206    #
207    us = source['url'].split('?')
208    source['path'] = path.dirname(us[0])
209    source['file'] = path.basename(us[0])
210    source['name'], source['ext'] = path.splitext(source['file'])
211    if len(us) > 1:
212        source['args'] = us[1:]
213    source['local'] = \
[f8bdb41]214        path.join(source['local_prefix'], 'git', source['file'])
[649a64c]215    source['symlink'] = source['local']
216
[d755065]217def _cvs_parser(source, pathkey, config, opts):
218    #
219    # Check local path
220    #
221    _local_path(source, pathkey, config)
[4ce931b]222    #
223    # Symlink.
224    #
225    if not source['url'].startswith('cvs://'):
226        raise error.general('invalid cvs path: %s' % (source['url']))
227    us = source['url'].split('?')
228    try:
229        url = us[0]
[c65b9eb]230        source['file'] = url[url[6:].index(':') + 7:]
[4ce931b]231        source['cvsroot'] = ':%s:' % (url[6:url[6:].index('/') + 6:])
232    except:
233        raise error.general('invalid cvs path: %s' % (source['url']))
[f077b2b]234    for a in us[1:]:
235        _as = a.split('=')
[c65b9eb]236        if _as[0] == 'module':
237            if len(_as) != 2:
238                raise error.general('invalid cvs module: %s' % (a))
239            source['module'] = _as[1]
240        elif _as[0] == 'src-prefix':
[f077b2b]241            if len(_as) != 2:
242                raise error.general('invalid cvs src-prefix: %s' % (a))
243            source['src_prefix'] = _as[1]
[c65b9eb]244        elif _as[0] == 'tag':
245            if len(_as) != 2:
246                raise error.general('invalid cvs tag: %s' % (a))
247            source['tag'] = _as[1]
248        elif _as[0] == 'date':
249            if len(_as) != 2:
250                raise error.general('invalid cvs date: %s' % (a))
251            source['date'] = _as[1]
252    if 'date' in source and 'tag' in source:
253        raise error.general('cvs URL cannot have a date and tag: %s' % (source['url']))
254    # Do here to ensure an ordered path, the URL can include options in any order
255    if 'module' in source:
256        source['file'] += '_%s' % (source['module'])
257    if 'tag' in source:
258        source['file'] += '_%s' % (source['tag'])
259    if 'date' in source:
260        source['file'] += '_%s' % (source['date'])
261    for c in '/@#%.-':
262        source['file'] = source['file'].replace(c, '_')
263    source['local'] = path.join(source['local_prefix'], 'cvs', source['file'])
[4ce931b]264    if 'src_prefix' in source:
[f077b2b]265        source['symlink'] = path.join(source['local'], source['src_prefix'])
[4ce931b]266    else:
267        source['symlink'] = source['local']
[649a64c]268
[d755065]269def _file_parser(source, pathkey, config, opts):
270    #
271    # Check local path
272    #
273    _local_path(source, pathkey, config)
[649a64c]274    #
[5b5d6bf]275    # Get the paths sorted.
[649a64c]276    #
[5b5d6bf]277    source['file'] = source['url'][6:]
[649a64c]278
279parsers = { 'http': _http_parser,
280            'ftp':  _http_parser,
[587aa5f]281            'pw':   _patchworks_parser,
[649a64c]282            'git':  _git_parser,
[4ce931b]283            'cvs':  _cvs_parser,
[f8bdb41]284            'file': _file_parser }
[649a64c]285
286def parse_url(url, pathkey, config, opts):
287    #
288    # Split the source up into the parts we need.
289    #
290    source = {}
291    source['url'] = url
[587aa5f]292    colon = url.find(':')
293    if url[colon + 1:colon + 3] != '//':
[d790668]294        raise error.general('malforned URL (no protocol prefix): %s' % (url))
[587aa5f]295    source['path'] = url[:colon + 3] + path.dirname(url[colon + 3:])
[649a64c]296    source['file'] = path.basename(url)
297    source['name'], source['ext'] = path.splitext(source['file'])
[9a15c40]298    if source['name'].endswith('.tar'):
299        source['name'] = source['name'][:-4]
300        source['ext'] = '.tar' + source['ext']
[649a64c]301    #
302    # Get the file. Checks the local source directory first.
303    #
304    source['local'] = None
305    for p in parsers:
306        if url.startswith(p):
307            source['type'] = p
[d755065]308            if parsers[p](source, pathkey, config, opts):
[649a64c]309                break
[d755065]310    source['script'] = ''
[649a64c]311    return source
312
313def _http_downloader(url, local, config, opts):
314    if path.exists(local):
315        return True
316    #
317    # Hack for GitHub.
318    #
319    if url.startswith('https://api.github.com'):
320        url = urlparse.urljoin(url, config.expand('tarball/%{version}'))
[c49e500]321    dst = os.path.relpath(path.host(local))
322    log.notice('download: %s -> %s' % (url, dst))
[649a64c]323    failed = False
[d755065]324    if _do_download(opts):
[649a64c]325        _in = None
326        _out = None
[c49e500]327        _length = None
328        _have = 0
329        _chunk_size = 256 * 1024
330        _chunk = None
331        _last_percent = 200.0
332        _last_msg = ''
333        _wipe_output = False
[649a64c]334        try:
[c49e500]335            try:
[d755065]336                _in = None
[3237c8e]337                _ssl_context = None
338                try:
339                    import ssl
340                    _ssl_context = ssl._create_unverified_context()
[bd16849]341                    _in = urllib2.urlopen(url, context = _ssl_context)
[3237c8e]342                except:
[bd16849]343                    _ssl_context = None
344                if _ssl_context is None:
345                    _in = urllib2.urlopen(url)
[d755065]346                if url != _in.geturl():
347                    log.notice(' redirect: %s' % (_in.geturl()))
[c49e500]348                _out = open(path.host(local), 'wb')
349                try:
350                    _length = int(_in.info().getheader('Content-Length').strip())
351                except:
352                    pass
353                while True:
354                    _msg = '\rdownloading: %s - %s ' % (dst, _humanize_bytes(_have))
355                    if _length:
356                        _percent = round((float(_have) / _length) * 100, 2)
357                        if _percent != _last_percent:
358                            _msg += 'of %s (%0.0f%%) ' % (_humanize_bytes(_length), _percent)
359                    if _msg != _last_msg:
360                        extras = (len(_last_msg) - len(_msg))
361                        log.stdout_raw('%s%s' % (_msg, ' ' * extras + '\b' * extras))
362                        _last_msg = _msg
363                    _chunk = _in.read(_chunk_size)
364                    if not _chunk:
365                        break
366                    _out.write(_chunk)
367                    _have += len(_chunk)
368                if _wipe_output:
369                    log.stdout_raw('\r%s\r' % (' ' * len(_last_msg)))
370                else:
371                    log.stdout_raw('\n')
372            except:
373                log.stdout_raw('\n')
374                raise
[649a64c]375        except IOError, err:
[5142bec]376            log.notice('download: %s: error: %s' % (url, str(err)))
[649a64c]377            if path.exists(local):
378                os.remove(path.host(local))
379            failed = True
380        except ValueError, err:
[5142bec]381            log.notice('download: %s: error: %s' % (url, str(err)))
[649a64c]382            if path.exists(local):
383                os.remove(path.host(local))
384            failed = True
385        except:
386            msg = 'download: %s: error' % (url)
[fa87409]387            log.stderr(msg)
[5142bec]388            log.notice(msg)
[d755065]389            if _in is not None:
390                _in.close()
[649a64c]391            if _out is not None:
392                _out.close()
393            raise
394        if _out is not None:
395            _out.close()
396        if _in is not None:
[d755065]397            _in.close()
[649a64c]398            del _in
399        if not failed:
400            if not path.isfile(local):
401                raise error.general('source is not a file: %s' % (path.host(local)))
[a083b52]402            if not _hash_check(path.basename(local), local, config.macros, False):
403                raise error.general('checksum failure file: %s' % (dst))
[649a64c]404    return not failed
405
406def _git_downloader(url, local, config, opts):
[d790668]407    repo = git.repo(local, opts, config.macros)
[649a64c]408    rlp = os.path.relpath(path.host(local))
409    us = url.split('?')
[d790668]410    #
411    # Handle the various git protocols.
412    #
413    # remove 'git' from 'git://xxxx/xxxx?protocol=...'
414    #
415    url_base = us[0][len('git'):]
416    for a in us[1:]:
417        _as = a.split('=')
418        if _as[0] == 'protocol':
419            if len(_as) != 2:
420                raise error.general('invalid git protocol option: %s' % (_as))
421            if _as[1] == 'none':
422                # remove the rest of the protocol header leaving nothing.
423                us[0] = url_base[len('://'):]
424            else:
425                if _as[1] not in ['ssh', 'git', 'http', 'https', 'ftp', 'ftps', 'rsync']:
426                    raise error.general('unknown git protocol: %s' % (_as[1]))
427                us[0] = _as[1] + url_base
[649a64c]428    if not repo.valid():
[5142bec]429        log.notice('git: clone: %s -> %s' % (us[0], rlp))
[d755065]430        if _do_download(opts):
[649a64c]431            repo.clone(us[0], local)
[53b918e]432    else:
[8c19df2]433        repo.clean(['-f', '-d'])
[53b918e]434        repo.reset('--hard')
435        repo.checkout('master')
[649a64c]436    for a in us[1:]:
437        _as = a.split('=')
[53b918e]438        if _as[0] == 'branch' or _as[0] == 'checkout':
439            if len(_as) != 2:
440                raise error.general('invalid git branch/checkout: %s' % (_as))
[5142bec]441            log.notice('git: checkout: %s => %s' % (us[0], _as[1]))
[d755065]442            if _do_download(opts):
[649a64c]443                repo.checkout(_as[1])
[53b918e]444        elif _as[0] == 'submodule':
445            if len(_as) != 2:
446                raise error.general('invalid git submodule: %s' % (_as))
447            log.notice('git: submodule: %s <= %s' % (us[0], _as[1]))
[d755065]448            if _do_download(opts):
[53b918e]449                repo.submodule(_as[1])
[649a64c]450        elif _as[0] == 'fetch':
[5142bec]451            log.notice('git: fetch: %s -> %s' % (us[0], rlp))
[d755065]452            if _do_download(opts):
[649a64c]453                repo.fetch()
[b0f9e30]454        elif _as[0] == 'merge':
455            log.notice('git: merge: %s' % (us[0]))
456            if _do_download(opts):
457                repo.merge()
458        elif _as[0] == 'pull':
459            log.notice('git: pull: %s' % (us[0]))
460            if _do_download(opts):
461                repo.pull()
[649a64c]462        elif _as[0] == 'reset':
463            arg = []
464            if len(_as) > 1:
465                arg = ['--%s' % (_as[1])]
[5142bec]466            log.notice('git: reset: %s' % (us[0]))
[d755065]467            if _do_download(opts):
[649a64c]468                repo.reset(arg)
[d790668]469        elif _as[0] == 'protocol':
470            pass
471        else:
472            raise error.general('invalid git option: %s' % (_as))
[649a64c]473    return True
474
[4ce931b]475def _cvs_downloader(url, local, config, opts):
476    rlp = os.path.relpath(path.host(local))
477    us = url.split('?')
478    module = None
479    tag = None
480    date = None
481    src_prefix = None
482    for a in us[1:]:
483        _as = a.split('=')
484        if _as[0] == 'module':
485            if len(_as) != 2:
486                raise error.general('invalid cvs module: %s' % (a))
487            module = _as[1]
488        elif _as[0] == 'src-prefix':
489            if len(_as) != 2:
490                raise error.general('invalid cvs src-prefix: %s' % (a))
491            src_prefix = _as[1]
492        elif _as[0] == 'tag':
493            if len(_as) != 2:
494                raise error.general('invalid cvs tag: %s' % (a))
495            tag = _as[1]
496        elif _as[0] == 'date':
497            if len(_as) != 2:
498                raise error.general('invalid cvs date: %s' % (a))
499            date = _as[1]
500    repo = cvs.repo(local, opts, config.macros, src_prefix)
501    if not repo.valid():
[5f44fcd]502        if not path.isdir(local):
503            log.notice('Creating source directory: %s' % \
504                           (os.path.relpath(path.host(local))))
[d755065]505            if _do_download(opts):
[5f44fcd]506                path.mkdir(local)
[f077b2b]507            log.notice('cvs: checkout: %s -> %s' % (us[0], rlp))
[d755065]508            if _do_download(opts):
[f077b2b]509                repo.checkout(':%s' % (us[0][6:]), module, tag, date)
[4ce931b]510    for a in us[1:]:
511        _as = a.split('=')
512        if _as[0] == 'update':
[5142bec]513            log.notice('cvs: update: %s' % (us[0]))
[d755065]514            if _do_download(opts):
[4ce931b]515                repo.update()
516        elif _as[0] == 'reset':
[5142bec]517            log.notice('cvs: reset: %s' % (us[0]))
[d755065]518            if _do_download(opts):
[4ce931b]519                repo.reset()
520    return True
521
[649a64c]522def _file_downloader(url, local, config, opts):
[12f253c]523    if not path.exists(local):
524        try:
525            src = url[7:]
526            dst = local
527            log.notice('download: copy %s -> %s' % (src, dst))
528            path.copy(src, dst)
529        except:
530            return False
[5b5d6bf]531    return True
[649a64c]532
533downloaders = { 'http': _http_downloader,
534                'ftp':  _http_downloader,
[587aa5f]535                'pw':   _http_downloader,
[649a64c]536                'git':  _git_downloader,
[4ce931b]537                'cvs':  _cvs_downloader,
538                'file': _file_downloader }
[649a64c]539
540def get_file(url, local, opts, config):
541    if local is None:
542        raise error.general('source/patch path invalid')
543    if not path.isdir(path.dirname(local)) and not opts.download_disabled():
[5142bec]544        log.notice('Creating source directory: %s' % \
545                       (os.path.relpath(path.host(path.dirname(local)))))
546    log.output('making dir: %s' % (path.host(path.dirname(local))))
[d755065]547    if _do_download(opts):
[649a64c]548        path.mkdir(path.dirname(local))
549    if not path.exists(local) and opts.download_disabled():
550        raise error.general('source not found: %s' % (path.host(local)))
551    #
[12f253c]552    # Check if a URL has been provided on the command line. If the package is
553    # release push to the start the RTEMS URL.
[649a64c]554    #
555    url_bases = opts.urls()
[12f253c]556    if version.released():
557        rtems_release_url = config.macros.expand('%{rtems_release_url}/%{rsb_version}/sources')
558        log.trace('release url: %s' % (rtems_release_url))
559        #
560        # If the URL being fetched is under the release path do not add the
561        # sources release path because it is already there.
562        #
563        if not url.startswith(rtems_release_url):
564            if url_bases is None:
565                url_bases = [rtems_release_url]
566            else:
567                url_bases.append(rtems_release_url)
[649a64c]568    urls = []
569    if url_bases is not None:
[12f253c]570        #
571        # Split up the URL we are being asked to download.
572        #
573        url_path = urlparse.urlsplit(url)[2]
574        slash = url_path.rfind('/')
575        if slash < 0:
576            url_file = url_path
577        else:
578            url_file = url_path[slash + 1:]
579        log.trace('url_file: %s' %(url_file))
[649a64c]580        for base in url_bases:
581            if base[-1:] != '/':
582                base += '/'
[12f253c]583            next_url = urlparse.urljoin(base, url_file)
584            log.trace('url: %s' %(next_url))
585            urls.append(next_url)
[53b918e]586    urls += url.split()
[5142bec]587    log.trace('_url: %s -> %s' % (','.join(urls), local))
[53b918e]588    for url in urls:
589        for dl in downloaders:
590            if url.startswith(dl):
591                if downloaders[dl](url, local, config, opts):
592                    return
[d755065]593    if _do_download(opts):
[649a64c]594        raise error.general('downloading %s: all paths have failed, giving up' % (url))
Note: See TracBrowser for help on using the repository browser.