You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

312 lines
10 KiB

"""Repository rule for Python autoconfiguration.
`python_configure` depends on the following environment variables:
* `PYTHON_BIN_PATH`: location of python binary.
* `PYTHON_LIB_PATH`: Location of python libraries.
"""
load(
"//third_party/remote_config:common.bzl",
"BAZEL_SH",
"PYTHON_BIN_PATH",
"PYTHON_LIB_PATH",
"TF_PYTHON_CONFIG_REPO",
"auto_config_fail",
"config_repo_label",
"execute",
"get_bash_bin",
"get_host_environ",
"get_python_bin",
"is_windows",
"raw_exec",
"read_dir",
)
def _genrule(src_dir, genrule_name, command, outs):
"""Returns a string with a genrule.
Genrule executes the given command and produces the given outputs.
"""
return (
"genrule(\n" +
' name = "' +
genrule_name + '",\n' +
" outs = [\n" +
outs +
"\n ],\n" +
' cmd = """\n' +
command +
'\n """,\n' +
")\n"
)
def _norm_path(path):
"""Returns a path with '/' and remove the trailing slash."""
path = path.replace("\\", "/")
if path[-1] == "/":
path = path[:-1]
return path
def _symlink_genrule_for_dir(
repository_ctx,
src_dir,
dest_dir,
genrule_name,
src_files = [],
dest_files = []):
"""Returns a genrule to symlink(or copy if on Windows) a set of files.
If src_dir is passed, files will be read from the given directory; otherwise
we assume files are in src_files and dest_files
"""
if src_dir != None:
src_dir = _norm_path(src_dir)
dest_dir = _norm_path(dest_dir)
files = "\n".join(read_dir(repository_ctx, src_dir))
# Create a list with the src_dir stripped to use for outputs.
dest_files = files.replace(src_dir, "").splitlines()
src_files = files.splitlines()
command = []
outs = []
for i in range(len(dest_files)):
if dest_files[i] != "":
# If we have only one file to link we do not want to use the dest_dir, as
# $(@D) will include the full path to the file.
dest = "$(@D)/" + dest_dir + dest_files[i] if len(dest_files) != 1 else "$(@D)/" + dest_files[i]
# Copy the headers to create a sandboxable setup.
cmd = "cp -f"
command.append(cmd + ' "%s" "%s"' % (src_files[i], dest))
outs.append(' "' + dest_dir + dest_files[i] + '",')
genrule = _genrule(
src_dir,
genrule_name,
" && ".join(command),
"\n".join(outs),
)
return genrule
def _get_python_lib(repository_ctx, python_bin):
"""Gets the python lib path."""
python_lib = get_host_environ(repository_ctx, PYTHON_LIB_PATH)
if python_lib != None:
return python_lib
# The interesting program to execute.
print_lib = [
"from __future__ import print_function",
"import site",
"import os",
"python_paths = []",
"if os.getenv('PYTHONPATH') is not None:",
" python_paths = os.getenv('PYTHONPATH').split(':')",
"try:",
" library_paths = site.getsitepackages()",
"except AttributeError:",
" from distutils.sysconfig import get_python_lib",
" library_paths = [get_python_lib()]",
"all_paths = set(python_paths + library_paths)",
"paths = []",
"for path in all_paths:",
" if os.path.isdir(path):",
" paths.append(path)",
"if len(paths) >=1:",
" print(paths[0])",
]
# The below script writes the above program to a file
# and executes it. This is to work around the limitation
# of not being able to upload files as part of execute.
cmd = "from os import linesep;"
cmd += "f = open('script.py', 'w');"
for line in print_lib:
cmd += "f.write(\"%s\" + linesep);" % line
cmd += "f.close();"
cmd += "from os import system;"
cmd += "system(\"%s script.py\");" % python_bin
result = execute(repository_ctx, [python_bin, "-c", cmd])
return result.stdout.strip()
def _check_python_lib(repository_ctx, python_lib):
"""Checks the python lib path."""
cmd = 'test -d "%s" -a -x "%s"' % (python_lib, python_lib)
result = raw_exec(repository_ctx, [get_bash_bin(repository_ctx), "-c", cmd])
if result.return_code == 1:
auto_config_fail("Invalid python library path: %s" % python_lib)
def _check_python_bin(repository_ctx, python_bin):
"""Checks the python bin path."""
cmd = '[[ -x "%s" ]] && [[ ! -d "%s" ]]' % (python_bin, python_bin)
result = raw_exec(repository_ctx, [get_bash_bin(repository_ctx), "-c", cmd])
if result.return_code == 1:
auto_config_fail("--define %s='%s' is not executable. Is it the python binary?" % (
PYTHON_BIN_PATH,
python_bin,
))
def _get_python_include(repository_ctx, python_bin):
"""Gets the python include path."""
result = execute(
repository_ctx,
[
python_bin,
"-c",
"from __future__ import print_function;" +
"from distutils import sysconfig;" +
"print(sysconfig.get_python_inc())",
],
error_msg = "Problem getting python include path.",
error_details = ("Is the Python binary path set up right? " +
"(See ./configure or " + PYTHON_BIN_PATH + ".) " +
"Is distutils installed?"),
)
return result.stdout.splitlines()[0]
def _get_python_import_lib_name(repository_ctx, python_bin):
"""Get Python import library name (pythonXY.lib) on Windows."""
result = execute(
repository_ctx,
[
python_bin,
"-c",
"import sys;" +
'print("python" + str(sys.version_info[0]) + ' +
' str(sys.version_info[1]) + ".lib")',
],
error_msg = "Problem getting python import library.",
error_details = ("Is the Python binary path set up right? " +
"(See ./configure or " + PYTHON_BIN_PATH + ".) "),
)
return result.stdout.splitlines()[0]
def _get_numpy_include(repository_ctx, python_bin):
"""Gets the numpy include path."""
return execute(
repository_ctx,
[
python_bin,
"-c",
"from __future__ import print_function;" +
"import numpy;" +
" print(numpy.get_include());",
],
error_msg = "Problem getting numpy include path.",
error_details = "Is numpy installed?",
).stdout.splitlines()[0]
def _create_local_python_repository(repository_ctx):
"""Creates the repository containing files set up to build with Python."""
# Resolve all labels before doing any real work. Resolving causes the
# function to be restarted with all previous state being lost. This
# can easily lead to a O(n^2) runtime in the number of labels.
build_tpl = repository_ctx.path(Label("//third_party/py:BUILD.tpl"))
python_bin = get_python_bin(repository_ctx)
_check_python_bin(repository_ctx, python_bin)
python_lib = _get_python_lib(repository_ctx, python_bin)
_check_python_lib(repository_ctx, python_lib)
python_include = _get_python_include(repository_ctx, python_bin)
numpy_include = _get_numpy_include(repository_ctx, python_bin) + "/numpy"
python_include_rule = _symlink_genrule_for_dir(
repository_ctx,
python_include,
"python_include",
"python_include",
)
python_import_lib_genrule = ""
# To build Python C/C++ extension on Windows, we need to link to python import library pythonXY.lib
# See https://docs.python.org/3/extending/windows.html
if is_windows(repository_ctx):
python_include = _norm_path(python_include)
python_import_lib_name = _get_python_import_lib_name(repository_ctx, python_bin)
python_import_lib_src = python_include.rsplit("/", 1)[0] + "/libs/" + python_import_lib_name
python_import_lib_genrule = _symlink_genrule_for_dir(
repository_ctx,
None,
"",
"python_import_lib",
[python_import_lib_src],
[python_import_lib_name],
)
numpy_include_rule = _symlink_genrule_for_dir(
repository_ctx,
numpy_include,
"numpy_include/numpy",
"numpy_include",
)
platform_constraint = ""
if repository_ctx.attr.platform_constraint:
platform_constraint = "\"%s\"" % repository_ctx.attr.platform_constraint
repository_ctx.template("BUILD", build_tpl, {
"%{PYTHON_BIN_PATH}": python_bin,
"%{PYTHON_INCLUDE_GENRULE}": python_include_rule,
"%{PYTHON_IMPORT_LIB_GENRULE}": python_import_lib_genrule,
"%{NUMPY_INCLUDE_GENRULE}": numpy_include_rule,
"%{PLATFORM_CONSTRAINT}": platform_constraint,
})
def _create_remote_python_repository(repository_ctx, remote_config_repo):
"""Creates pointers to a remotely configured repo set up to build with Python.
"""
repository_ctx.template("BUILD", config_repo_label(remote_config_repo, ":BUILD"), {})
def _python_autoconf_impl(repository_ctx):
"""Implementation of the python_autoconf repository rule."""
if get_host_environ(repository_ctx, TF_PYTHON_CONFIG_REPO) != None:
_create_remote_python_repository(
repository_ctx,
get_host_environ(repository_ctx, TF_PYTHON_CONFIG_REPO),
)
else:
_create_local_python_repository(repository_ctx)
_ENVIRONS = [
BAZEL_SH,
PYTHON_BIN_PATH,
PYTHON_LIB_PATH,
]
local_python_configure = repository_rule(
implementation = _create_local_python_repository,
environ = _ENVIRONS,
attrs = {
"environ": attr.string_dict(),
"platform_constraint": attr.string(),
},
)
remote_python_configure = repository_rule(
implementation = _create_local_python_repository,
environ = _ENVIRONS,
remotable = True,
attrs = {
"environ": attr.string_dict(),
"platform_constraint": attr.string(),
},
)
python_configure = repository_rule(
implementation = _python_autoconf_impl,
environ = _ENVIRONS + [TF_PYTHON_CONFIG_REPO],
attrs = {
"platform_constraint": attr.string(),
},
)
"""Detects and configures the local Python.
Add the following to your WORKSPACE FILE:
```python
python_configure(name = "local_config_python")
```
Args:
name: A unique name for this workspace rule.
"""