1# Copyright 2013 The Servo Project Developers. See the COPYRIGHT
2# file at the top-level directory of this distribution.
3#
4# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7# option. This file may not be copied, modified, or distributed
8# except according to those terms.
9
10from __future__ import absolute_import, print_function, unicode_literals
11
12import os
13import os.path
14import platform
15import shutil
16from socket import error as socket_error
17import stat
18import StringIO
19import sys
20import zipfile
21import urllib2
22import certifi
23
24
25try:
26    from ssl import HAS_SNI
27except ImportError:
28    HAS_SNI = False
29
30# The cafile parameter was added in 2.7.9
31if HAS_SNI and sys.version_info >= (2, 7, 9):
32    STATIC_RUST_LANG_ORG_DIST = "https://static.rust-lang.org/dist"
33    URLOPEN_KWARGS = {"cafile": certifi.where()}
34else:
35    STATIC_RUST_LANG_ORG_DIST = "https://static-rust-lang-org.s3.amazonaws.com/dist"
36    URLOPEN_KWARGS = {}
37
38
39def remove_readonly(func, path, _):
40    "Clear the readonly bit and reattempt the removal"
41    os.chmod(path, stat.S_IWRITE)
42    func(path)
43
44
45def delete(path):
46    if os.path.isdir(path) and not os.path.islink(path):
47        shutil.rmtree(path, onerror=remove_readonly)
48    else:
49        os.remove(path)
50
51
52def host_platform():
53    os_type = platform.system().lower()
54    if os_type == "linux":
55        os_type = "unknown-linux-gnu"
56    elif os_type == "darwin":
57        os_type = "apple-darwin"
58    elif os_type == "android":
59        os_type = "linux-androideabi"
60    elif os_type == "windows":
61        os_type = "pc-windows-msvc"
62    elif os_type == "freebsd":
63        os_type = "unknown-freebsd"
64    else:
65        os_type = "unknown"
66    return os_type
67
68
69def host_triple():
70    os_type = host_platform()
71    cpu_type = platform.machine().lower()
72    if cpu_type in ["i386", "i486", "i686", "i768", "x86"]:
73        cpu_type = "i686"
74    elif cpu_type in ["x86_64", "x86-64", "x64", "amd64"]:
75        cpu_type = "x86_64"
76    elif cpu_type == "arm":
77        cpu_type = "arm"
78    elif cpu_type == "aarch64":
79        cpu_type = "aarch64"
80    else:
81        cpu_type = "unknown"
82
83    return "{}-{}".format(cpu_type, os_type)
84
85
86def download(desc, src, writer, start_byte=0):
87    if start_byte:
88        print("Resuming download of {} ...".format(src))
89    else:
90        print("Downloading {} ...".format(src))
91    dumb = (os.environ.get("TERM") == "dumb") or (not sys.stdout.isatty())
92
93    try:
94        req = urllib2.Request(src)
95        if start_byte:
96            req = urllib2.Request(src, headers={'Range': 'bytes={}-'.format(start_byte)})
97        resp = urllib2.urlopen(req, **URLOPEN_KWARGS)
98
99        fsize = None
100        if resp.info().getheader('Content-Length'):
101            fsize = int(resp.info().getheader('Content-Length').strip()) + start_byte
102
103        recved = start_byte
104        chunk_size = 8192
105
106        while True:
107            chunk = resp.read(chunk_size)
108            if not chunk:
109                break
110            recved += len(chunk)
111            if not dumb:
112                if fsize is not None:
113                    pct = recved * 100.0 / fsize
114                    print("\rDownloading %s: %5.1f%%" % (desc, pct), end="")
115
116                sys.stdout.flush()
117            writer.write(chunk)
118
119        if not dumb:
120            print()
121    except urllib2.HTTPError, e:
122        print("Download failed ({}): {} - {}".format(e.code, e.reason, src))
123        if e.code == 403:
124            print("No Rust compiler binary available for this platform. "
125                  "Please see https://github.com/servo/servo/#prerequisites")
126        sys.exit(1)
127    except urllib2.URLError, e:
128        print("Error downloading {}: {}. The failing URL was: {}".format(desc, e.reason, src))
129        sys.exit(1)
130    except socket_error, e:
131        print("Looks like there's a connectivity issue, check your Internet connection. {}".format(e))
132        sys.exit(1)
133    except KeyboardInterrupt:
134        writer.flush()
135        raise
136
137
138def download_bytes(desc, src):
139    content_writer = StringIO.StringIO()
140    download(desc, src, content_writer)
141    return content_writer.getvalue()
142
143
144def download_file(desc, src, dst):
145    tmp_path = dst + ".part"
146    try:
147        start_byte = os.path.getsize(tmp_path)
148        with open(tmp_path, 'ab') as fd:
149            download(desc, src, fd, start_byte=start_byte)
150    except os.error:
151        with open(tmp_path, 'wb') as fd:
152            download(desc, src, fd)
153    os.rename(tmp_path, dst)
154
155
156def extract(src, dst, movedir=None):
157    assert src.endswith(".zip")
158    zipfile.ZipFile(src).extractall(dst)
159
160    if movedir:
161        for f in os.listdir(movedir):
162            frm = os.path.join(movedir, f)
163            to = os.path.join(dst, f)
164            os.rename(frm, to)
165        os.rmdir(movedir)
166
167    os.remove(src)
168