aboutsummaryrefslogtreecommitdiffstats
path: root/mach
blob: df80ac13a880c3af5888e0ee52535ccea66a360e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#!/bin/sh
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# The beginning of this script is both valid shell and valid python,
# such that the script starts with the shell and is reexecuted with
# the right python.
''':' && if [ ! -z "$MSYSTEM" ] ; then exec python "$0" "$@" ; else which python2.7 > /dev/null 2> /dev/null && exec python2.7 "$0" "$@" || exec python "$0" "$@" ; fi
'''

from __future__ import print_function, unicode_literals

import itertools
import os
import subprocess
import sys


def main(args):
    topdir = os.path.abspath(os.path.dirname(sys.argv[0]))
    sys.path.insert(0, os.path.join(topdir, "python"))
    import mach_bootstrap
    mach = mach_bootstrap.bootstrap(topdir)
    sys.exit(mach.run(sys.argv[1:]))

def validate_pair(ob):
  if not (len(ob) == 2):
    print("Unexpected result:", ob, file=sys.stderr)
    return False
  else:
    return True

def consume(iter):
  try:
    while True: next(iter)
  except StopIteration:
    pass

def get_environment_from_batch_command(env_cmd, initial=None):
  """
  Take a command (either a single command or list of arguments)
  and return the environment created after running that command.
  Note that if the command must be a batch file or .cmd file, or the
  changes to the environment will not be captured.
  If initial is supplied, it is used as the initial environment passed
  to the child process.
  """
  if not isinstance(env_cmd, (list, tuple)):
    env_cmd = [env_cmd]
  # Construct the command that will alter the environment.
  env_cmd = subprocess.list2cmdline(env_cmd)
  # Create a tag so we can tell in the output when the proc is done.
  tag = 'END OF BATCH COMMAND'
  # Construct a cmd.exe command to do accomplish this.
  cmd = 'cmd.exe /s /c "{env_cmd} && echo "{tag}" && set"'.format(**vars())
  # Launch the process.
  proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=initial)
  # Parse the output sent to stdout.
  lines = proc.stdout
  # Consume whatever output occurs until the tag is reached.
  consume(itertools.takewhile(lambda l: tag not in l, lines))
  # Define a way to handle each KEY=VALUE line.
  handle_line = lambda l: l.rstrip().split('=',1)
  # Parse key/values into pairs.
  pairs = map(handle_line, lines)
  # Make sure the pairs are valid.
  valid_pairs = filter(validate_pair, pairs)
  # Construct a dictionary of the pairs.
  result = dict(valid_pairs)
  # Let the process finish.
  proc.communicate()
  return result

def get_vs_env(vs_version, arch):
  """
  Returns the env object for VS building environment.
  The vs_version can be strings like "[15.0,16.0)", meaning 2017, but not the next version.
  The arch has to be one of "x86", "amd64", "arm", "x86_amd64", "x86_arm", "amd64_x86",
  "amd64_arm", e.g. the args passed to vcvarsall.bat.
  """

  # vswhere can't handle spaces, like "[15.0, 16.0)" should become "[15.0,16.0)"
  vs_version = vs_version.replace(" ", "")

  # Find visual studio
  proc = subprocess.Popen(
    "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vswhere.exe "
    "-property installationPath "
    "-requires Microsoft.VisualStudio.Component.VC.CoreIde "
    "-format value "
    "-version {0}".format(vs_version),
    stdout=subprocess.PIPE)

  location = proc.stdout.readline().rstrip()
  # Launch the process.
  vsvarsall = "{0}\\VC\\Auxiliary\\Build\\vcvarsall.bat".format(location)

  return get_environment_from_batch_command([vsvarsall, arch])

def import_vs_env():
    if sys.platform != 'win32':
        return

    if ('PROGRAMFILES(X86)' in os.environ) == False:
        print('32-bit Windows is currently unsupported.')
        sys.exit(-1)

    arch = 'x64'
    if os.path.isfile("C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vswhere.exe"):
        env = get_vs_env('[15.0,16.0)', arch)
        os.environ.update(env)
    elif 'VS140COMNTOOLS' in os.environ:
        vs14vsvarsall = os.path.abspath(os.path.join(os.environ['VS140COMNTOOLS'], '..\\..\\VC\\vcvarsall.bat'))
        env = get_environment_from_batch_command([vs14vsvarsall, arch])
        os.environ.update(env)
    else:
        print("Visual Studio 2017 is not installed.\nDownload and install Visual Studio 2017 from https://www.visualstudio.com/")
        sys.exit(-1)

if __name__ == '__main__':
    sys.dont_write_bytecode = True
    import_vs_env()

    if sys.platform == 'win32':
        # This is a complete hack to work around the fact that Windows
        # multiprocessing needs to import the original module (ie: this
        # file), but only works if it has a .py extension.
        #
        # We do this by a sort of two-level function interposing. The first
        # level interposes forking.get_command_line() with our version defined
        # in my_get_command_line(). Our version of get_command_line will
        # replace the command string with the contents of the fork_interpose()
        # function to be used in the subprocess.
        #
        # The subprocess then gets an interposed imp.find_module(), which we
        # hack up to find 'mach' without the .py extension, since we already
        # know where it is (it's us!). If we're not looking for 'mach', then
        # the original find_module will suffice.
        #
        # See also: http://bugs.python.org/issue19946
        # And: https://bugzilla.mozilla.org/show_bug.cgi?id=914563
        import inspect
        from multiprocessing import forking
        global orig_command_line

        def fork_interpose():
            import imp
            import os
            import sys
            orig_find_module = imp.find_module
            def my_find_module(name, dirs):
                if name == 'mach':
                    path = os.path.join(dirs[0], 'mach')
                    f = open(path)
                    return (f, path, ('', 'r', imp.PY_SOURCE))
                return orig_find_module(name, dirs)

            # Don't allow writing bytecode file for mach module.
            orig_load_module = imp.load_module
            def my_load_module(name, file, path, description):
                # multiprocess.forking invokes imp.load_module manually and
                # hard-codes the name __parents_main__ as the module name.
                if name == '__parents_main__':
                    old_bytecode = sys.dont_write_bytecode
                    sys.dont_write_bytecode = True
                    try:
                        return orig_load_module(name, file, path, description)
                    finally:
                        sys.dont_write_bytecode = old_bytecode

                return orig_load_module(name, file, path, description)

            imp.find_module = my_find_module
            imp.load_module = my_load_module
            from multiprocessing.forking import main; main()

        def my_get_command_line():
            fork_code, lineno = inspect.getsourcelines(fork_interpose)
            # Remove the first line (for 'def fork_interpose():') and the three
            # levels of indentation (12 spaces).
            fork_string = ''.join(x[12:] for x in fork_code[1:])
            cmdline = orig_command_line()
            cmdline[2] = fork_string
            return cmdline
        orig_command_line = forking.get_command_line
        forking.get_command_line = my_get_command_line

    main(sys.argv)