blob: dac21c9a83817994fe9fb26a57425bc086686835 [file] [log] [blame]
#!/usr/bin/env python
import os
import re
import tempfile
import shutil
import sys
import subprocess
import zipfile
# Return True if running on Windows
def IsWindows():
return os.name == 'nt'
def GetWindowsPathWithUNCPrefix(path):
"""
Adding UNC prefix after getting a normalized absolute Windows path,
it's no-op for non-Windows platforms or if running under python2.
"""
path = path.strip()
# No need to add prefix for non-Windows platforms.
# And \\?\ doesn't work in python 2
if not IsWindows() or sys.version_info[0] < 3:
return path
# Lets start the unicode fun
unicode_prefix = "\\\\?\\"
if path.startswith(unicode_prefix):
return path
# os.path.abspath returns a normalized absolute path
return unicode_prefix + os.path.abspath(path)
def HasWindowsExecutableExtension(path):
return path.endswith('.exe') or path.endswith('.com') or path.endswith('.bat')
PYTHON_BINARY = '%python_binary%'
if IsWindows() and not HasWindowsExecutableExtension(PYTHON_BINARY):
PYTHON_BINARY = PYTHON_BINARY + '.exe'
# Find a file in a given search path.
def SearchPath(name):
search_path = os.getenv('PATH', os.defpath).split(os.pathsep)
for directory in search_path:
if directory == '': continue
path = os.path.join(directory, name)
if os.path.isfile(path) and os.access(path, os.X_OK):
return path
return None
def IsRunningFromZip():
return %is_zipfile%
# Find the real Python binary if it's not a normal absolute path
def FindPythonBinary(module_space):
if PYTHON_BINARY.startswith('//'):
# Case 1: Path is a label. Not supported yet.
raise AssertionError(
'Bazel does not support execution of Python interpreters via labels yet')
elif os.path.isabs(PYTHON_BINARY):
# Case 2: Absolute path.
return PYTHON_BINARY
# Use normpath() to convert slashes to os.sep on Windows.
elif os.sep in os.path.normpath(PYTHON_BINARY):
# Case 3: Path is relative to the repo root.
return os.path.join(module_space, PYTHON_BINARY)
else:
# Case 4: Path has to be looked up in the search path.
return SearchPath(PYTHON_BINARY)
def CreatePythonPathEntries(python_imports, module_space):
parts = python_imports.split(':');
return [module_space] + ["%s/%s" % (module_space, path) for path in parts]
# Find the runfiles tree
def FindModuleSpace():
stub_filename = sys.argv[0]
if not os.path.isabs(stub_filename):
stub_filename = os.path.join(os.getcwd(), stub_filename)
while True:
module_space = stub_filename + ('.exe' if IsWindows() else '') + '.runfiles'
if os.path.isdir(module_space):
return module_space
runfiles_pattern = r'(.*\.runfiles)' + (r'\\' if IsWindows() else '/') + '.*'
matchobj = re.match(runfiles_pattern, stub_filename)
if matchobj:
return matchobj.group(1)
if not os.path.islink(stub_filename):
break
target = os.readlink(stub_filename)
if os.path.isabs(target):
stub_filename = target
else:
stub_filename = os.path.join(os.path.dirname(stub_filename), target)
raise AssertionError('Cannot find .runfiles directory for %s' % sys.argv[0])
# Create the runfiles tree by extracting the zip file
def CreateModuleSpace():
ZIP_RUNFILES_DIRECTORY_NAME = "runfiles"
temp_dir = tempfile.mkdtemp("", "Bazel.runfiles_")
zf = zipfile.ZipFile(GetWindowsPathWithUNCPrefix(os.path.dirname(__file__)))
zf.extractall(GetWindowsPathWithUNCPrefix(temp_dir))
return os.path.join(temp_dir, ZIP_RUNFILES_DIRECTORY_NAME)
# Returns repository roots to add to the import path.
def GetRepositoriesImports(module_space, import_all):
if import_all:
repo_dirs = [os.path.join(module_space, d) for d in os.listdir(module_space)]
return [d for d in repo_dirs if os.path.isdir(d)]
return [os.path.join(module_space, "%workspace_name%")]
# Finds the runfiles manifest or the runfiles directory.
def RunfilesEnvvar(module_space):
# If this binary is the data-dependency of another one, the other sets
# RUNFILES_MANIFEST_FILE or RUNFILES_DIR for our sake.
runfiles = os.environ.get('RUNFILES_MANIFEST_FILE', None)
if runfiles:
return ('RUNFILES_MANIFEST_FILE', runfiles)
runfiles = os.environ.get('RUNFILES_DIR', None)
if runfiles:
return ('RUNFILES_DIR', runfiles)
# If running from a zip, there's no manifest file.
if IsRunningFromZip():
return ('RUNFILES_DIR', module_space)
# Look for the runfiles "output" manifest, argv[0] + ".runfiles_manifest"
runfiles = module_space + '_manifest'
if os.path.exists(runfiles):
return ('RUNFILES_MANIFEST_FILE', runfiles)
# Look for the runfiles "input" manifest, argv[0] + ".runfiles/MANIFEST"
runfiles = os.path.join(module_space, 'MANIFEST')
if os.path.exists(runfiles):
return ('RUNFILES_DIR', runfiles)
# If running in a sandbox and no environment variables are set, then
# Look for the runfiles next to the binary.
if module_space.endswith('.runfiles') and os.path.isdir(module_space):
return ('RUNFILES_DIR', module_space)
return (None, None)
def Main():
args = sys.argv[1:]
new_env = {}
if IsRunningFromZip():
module_space = CreateModuleSpace()
else:
module_space = FindModuleSpace()
python_imports = '%imports%'
python_path_entries = CreatePythonPathEntries(python_imports, module_space)
python_path_entries += GetRepositoriesImports(module_space, %import_all%)
python_path_entries = [GetWindowsPathWithUNCPrefix(d) for d in python_path_entries]
old_python_path = os.environ.get('PYTHONPATH')
python_path = os.pathsep.join(python_path_entries)
if old_python_path:
python_path += os.pathsep + old_python_path
if IsWindows():
python_path = python_path.replace("/", os.sep)
new_env['PYTHONPATH'] = python_path
runfiles_envkey, runfiles_envvalue = RunfilesEnvvar(module_space)
if runfiles_envkey:
new_env[runfiles_envkey] = runfiles_envvalue
# Now look for my main python source file.
# The magic string percent-main-percent is replaced with the filename of the
# main file of the Python binary in BazelPythonSemantics.java.
rel_path = '%main%'
if IsWindows():
rel_path = rel_path.replace("/", os.sep)
main_filename = os.path.join(module_space, rel_path)
main_filename = GetWindowsPathWithUNCPrefix(main_filename)
assert os.path.exists(main_filename), \
'Cannot exec() %r: file not found.' % main_filename
assert os.access(main_filename, os.R_OK), \
'Cannot exec() %r: file not readable.' % main_filename
program = python_program = FindPythonBinary(module_space)
if python_program is None:
raise AssertionError('Could not find python binary: ' + PYTHON_BINARY)
args = [python_program, main_filename] + args
os.environ.update(new_env)
try:
sys.stdout.flush()
if IsRunningFromZip():
# If RUN_UNDER_RUNFILES equals 1, it means we need to
# change directory to the right runfiles directory.
# (So that the data files are accessible)
if os.environ.get("RUN_UNDER_RUNFILES") == "1":
os.chdir(os.path.join(module_space, "%workspace_name%"))
retCode = subprocess.call(args)
shutil.rmtree(os.path.dirname(module_space), True)
exit(retCode)
else:
if IsWindows():
# On Windows, os.execv doesn't handle arguments with spaces correctly,
# and it actually starts a subprocess just like subprocess.call.
exit(subprocess.call(args))
else:
os.execv(args[0], args)
except EnvironmentError:
# This works from Python 2.4 all the way to 3.x.
e = sys.exc_info()[1]
# This exception occurs when os.execv() fails for some reason.
if not getattr(e, 'filename', None):
e.filename = program # Add info to error message
raise
if __name__ == '__main__':
Main()