#!/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)