1#!/usr/bin/env python
2# This Source Code Form is subject to the terms of the Mozilla Public
3# License, v. 2.0. If a copy of the MPL was not distributed with this file,
4# You can obtain one at http://mozilla.org/MPL/2.0/.
5
6# This script provides one-line bootstrap support to configure systems to build
7# the tree.
8#
9# The role of this script is to load the Python modules containing actual
10# bootstrap support. It does this through various means, including fetching
11# content from the upstream source repository.
12
13from __future__ import absolute_import, print_function, unicode_literals
14
15WRONG_PYTHON_VERSION_MESSAGE = '''
16Bootstrap currently only runs on Python 2.7 or Python 3.5+.
17Please try re-running with python2.7 or python3.5+.
18
19If these aren't available on your system, you may need to install them.
20Look for a "python2" or "python3.5" package in your package manager.
21'''
22
23import sys
24
25major, minor = sys.version_info[:2]
26if (major == 2 and minor < 7) or (major == 3 and minor < 5):
27    print(WRONG_PYTHON_VERSION_MESSAGE)
28    sys.exit(1)
29
30import os
31import shutil
32import tempfile
33import zipfile
34
35from io import BytesIO
36from optparse import OptionParser
37
38# NOTE: This script is intended to be run with a vanilla Python install.  We
39# have to rely on the standard library instead of Python 2+3 helpers like
40# the six module.
41try:
42    from urllib2 import urlopen
43except ImportError:
44    from urllib.request import urlopen
45
46
47# The next two variables define where in the repository the Python files
48# reside. This is used to remotely download file content when it isn't
49# available locally.
50REPOSITORY_PATH_PREFIX = 'python/mozboot/'
51
52TEMPDIR = None
53
54
55def setup_proxy():
56    # Some Linux environments define ALL_PROXY, which is a SOCKS proxy
57    # intended for all protocols. Python doesn't currently automatically
58    # detect this like it does for http_proxy and https_proxy.
59    if 'ALL_PROXY' in os.environ and 'https_proxy' not in os.environ:
60        os.environ['https_proxy'] = os.environ['ALL_PROXY']
61    if 'ALL_PROXY' in os.environ and 'http_proxy' not in os.environ:
62        os.environ['http_proxy'] = os.environ['ALL_PROXY']
63
64
65def fetch_files(repo_url, repo_rev, repo_type):
66    setup_proxy()
67    repo_url = repo_url.rstrip('/')
68
69    files = {}
70
71    if repo_type == 'hgweb':
72        url = repo_url + '/archive/%s.zip/python/mozboot' % repo_rev
73        req = urlopen(url=url, timeout=30)
74        data = BytesIO(req.read())
75        data.seek(0)
76        zip = zipfile.ZipFile(data, 'r')
77        for f in zip.infolist():
78            # The paths are prefixed with the repo and revision name before the
79            # directory name.
80            offset = f.filename.find(REPOSITORY_PATH_PREFIX) + len(REPOSITORY_PATH_PREFIX)
81            name = f.filename[offset:]
82
83            # We only care about the Python modules.
84            if not name.startswith('mozboot/'):
85                continue
86
87            files[name] = zip.read(f)
88
89        # Retrieve distro script
90        url = repo_url + '/archive/%s.zip/third_party/python/distro/distro.py' % repo_rev
91        req = urlopen(url=url, timeout=30)
92        data = BytesIO(req.read())
93        data.seek(0)
94        zip = zipfile.ZipFile(data, 'r')
95        files["distro.py"] = zip.read(zip.infolist()[0])
96
97    else:
98        raise NotImplementedError('Not sure how to handle repo type.', repo_type)
99
100    return files
101
102
103def ensure_environment(repo_url=None, repo_rev=None, repo_type=None):
104    """Ensure we can load the Python modules necessary to perform bootstrap."""
105
106    try:
107        from mozboot.bootstrap import Bootstrapper
108        return Bootstrapper
109    except ImportError:
110        # The first fallback is to assume we are running from a tree checkout
111        # and have the files in a sibling directory.
112        pardir = os.path.join(os.path.dirname(__file__), os.path.pardir)
113        include = os.path.normpath(pardir)
114
115        sys.path.append(include)
116        try:
117            from mozboot.bootstrap import Bootstrapper
118            return Bootstrapper
119        except ImportError:
120            sys.path.pop()
121
122            # The next fallback is to download the files from the source
123            # repository.
124            files = fetch_files(repo_url, repo_rev, repo_type)
125
126            # Install them into a temporary location. They will be deleted
127            # after this script has finished executing.
128            global TEMPDIR
129            TEMPDIR = tempfile.mkdtemp()
130
131            for relpath in files.keys():
132                destpath = os.path.join(TEMPDIR, relpath)
133                destdir = os.path.dirname(destpath)
134
135                if not os.path.exists(destdir):
136                    os.makedirs(destdir)
137
138                with open(destpath, 'wb') as fh:
139                    fh.write(files[relpath])
140
141            # This should always work.
142            sys.path.append(TEMPDIR)
143            from mozboot.bootstrap import Bootstrapper
144            return Bootstrapper
145
146
147def main(args):
148    parser = OptionParser()
149    parser.add_option('-r', '--repo-url', dest='repo_url',
150                      default='https://hg.mozilla.org/mozilla-central/',
151                      help='Base URL of source control repository where bootstrap files can '
152                      'be downloaded.')
153    parser.add_option('--repo-rev', dest='repo_rev',
154                      default='default',
155                      help='Revision of files in repository to fetch')
156    parser.add_option('--repo-type', dest='repo_type',
157                      default='hgweb',
158                      help='The type of the repository. This defines how we fetch file '
159                      'content. Like --repo, you should not need to set this.')
160
161    parser.add_option('--application-choice', dest='application_choice',
162                      help='Pass in an application choice (see mozboot.bootstrap.APPLICATIONS) '
163                      'instead of using the default interactive prompt.')
164    parser.add_option('--vcs', dest='vcs', default=None,
165                      help='VCS (hg or git) to use for downloading the source code, '
166                      'instead of using the default interactive prompt.')
167    parser.add_option('--no-interactive', dest='no_interactive', action='store_true',
168                      help='Answer yes to any (Y/n) interactive prompts.')
169    parser.add_option('--debug', dest='debug', action='store_true',
170                      help='Print extra runtime information useful for debugging and '
171                      'bug reports.')
172
173    options, leftover = parser.parse_args(args)
174
175    try:
176        try:
177            cls = ensure_environment(options.repo_url, options.repo_rev,
178                                     options.repo_type)
179        except Exception as e:
180            print('Could not load the bootstrap Python environment.\n')
181            print('This should never happen. Consider filing a bug.\n')
182            print('\n')
183
184            if options.debug:
185                # Raise full tracebacks during debugging and for bug reporting.
186                raise
187
188            print(e)
189            return 1
190
191        dasboot = cls(choice=options.application_choice, no_interactive=options.no_interactive,
192                      vcs=options.vcs)
193        dasboot.bootstrap()
194
195        return 0
196    finally:
197        if TEMPDIR is not None:
198            shutil.rmtree(TEMPDIR)
199
200
201if __name__ == '__main__':
202    sys.exit(main(sys.argv))
203