Skip to content

Additional Hermeticity changes #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
workspace(name = "pybind11_bazel")
119 changes: 80 additions & 39 deletions python_configure.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,61 @@

`python_configure` depends on the following environment variables:

* `PYTHON_BIN_PATH`: location of python binary.
* `PYTHON_BIN_PATH`: Location of python binary.
* `PYTHON_LIB_PATH`: Location of python libraries.

These can be directly set with the `python_interpreter_target` and
`python_library_target` respectively.
"""

_BAZEL_SH = "BAZEL_SH"
_PYTHON_BIN_PATH = "PYTHON_BIN_PATH"
_PYTHON_CONFIG_BIN_PATH = "PYTHON_CONFIG_BIN_PATH"
_PYTHON_LIB_PATH = "PYTHON_LIB_PATH"

# TODO: Move scripts to respective files and expose to rule.
_INCLUDE_SCRIPT = """
from __future__ import print_function
from distutils import sysconfig
import shutil

python_inc_dir = sysconfig.get_python_inc()

# NOTE: A `genrule` coule potentially set a canonial path for python as follows:
# config_h_path = sysconfig.get_config_h_filename()
# shutil.copyfile(config_h_path, python_inc_dir + "/pyconfig.h")
# However, this should be a bit more defensive and check if the file exists.

print(python_inc_dir)"""

_LIBRARY_SCRIPT = """
from __future__ import print_function
import site
import os

try:
input = raw_input
except NameError:
pass

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])
"""

def _tpl(repository_ctx, tpl, substitutions = {}, out = None):
if not out:
out = tpl
Expand Down Expand Up @@ -96,7 +142,8 @@ def _genrule(src_dir, genrule_name, command, outs, local):
"genrule(\n" +
' name = "' +
genrule_name + '",\n' + (
" local = 1,\n" if local else "") +
" local = 1,\n" if local else ""
) +
" outs = [\n" +
outs +
"\n ],\n" +
Expand Down Expand Up @@ -194,37 +241,26 @@ def _get_bash_bin(repository_ctx):

def _get_python_lib(repository_ctx, python_bin):
"""Gets the python lib path."""
python_lib = repository_ctx.os.environ.get(_PYTHON_LIB_PATH)
if python_lib != None:
return python_lib
print_lib = ("<<END\n" +
"from __future__ import print_function\n" +
"import site\n" +
"import os\n" +
"\n" +
"try:\n" +
" input = raw_input\n" +
"except NameError:\n" +
" pass\n" +
"\n" +
"python_paths = []\n" +
"if os.getenv('PYTHONPATH') is not None:\n" +
" python_paths = os.getenv('PYTHONPATH').split(':')\n" +
"try:\n" +
" library_paths = site.getsitepackages()\n" +
"except AttributeError:\n" +
" from distutils.sysconfig import get_python_lib\n" +
" library_paths = [get_python_lib()]\n" +
"all_paths = set(python_paths + library_paths)\n" +
"paths = []\n" +
"for path in all_paths:\n" +
" if os.path.isdir(path):\n" +
" paths.append(path)\n" +
"if len(paths) >=1:\n" +
" print(paths[0])\n" +
"END")
cmd = "%s - %s" % (python_bin, print_lib)
result = repository_ctx.execute([_get_bash_bin(repository_ctx), "-c", cmd])
if repository_ctx.attr.python_library_target != None:
return str(repository_ctx.path(repository_ctx.attr.python_library_target))

# If an interpreter is explicitly passed down, we should should not regard
# the environmental variable.
if repository_ctx.attr.python_interpreter_target == None:
python_lib = repository_ctx.os.environ.get(_PYTHON_LIB_PATH)
if python_lib:
return python_lib

result = _execute(
repository_ctx,
[
python_bin,
"-c",
_LIBRARY_SCRIPT,
],
error_msg = "Unable to detect library path.",
error_details = ("Is Python installed correctly?"),
)
return result.stdout.strip("\n")

def _check_python_lib(repository_ctx, python_lib):
Expand All @@ -251,9 +287,7 @@ def _get_python_include(repository_ctx, python_bin):
[
python_bin,
"-c",
"from __future__ import print_function;" +
"from distutils import sysconfig;" +
"print(sysconfig.get_python_inc())",
_INCLUDE_SCRIPT,
],
error_msg = "Problem getting python include path.",
error_details = ("Is the Python binary path set up right? " +
Expand Down Expand Up @@ -314,6 +348,7 @@ def _get_embed_flags(repository_ctx, python_config):
# See https://github.com/python/cpython/pull/13500
link_cmd = python_config + " --ldflags --embed"

# TODO: Resolve ctx.execute vs _execute.
err = repository_ctx.execute([_get_bash_bin(repository_ctx), "-c", err_cmd]).stdout.strip("\n")
compiler_flags = repository_ctx.execute([_get_bash_bin(repository_ctx), "-c", comp_cmd]).stdout.strip("\n")
linker_flags = repository_ctx.execute([_get_bash_bin(repository_ctx), "-c", link_cmd]).stdout.strip("\n")
Expand Down Expand Up @@ -395,8 +430,9 @@ python_configure = repository_rule(
_PYTHON_LIB_PATH,
],
attrs = {
"python_version": attr.string(default=""),
"python_version": attr.string(default = ""),
"python_interpreter_target": attr.label(),
"python_library_target": attr.string(),
},
)
"""Detects and configures the local Python.
Expand All @@ -416,6 +452,11 @@ Args:
By default, will build for whatever Python version is returned by
`which python`.
python_interpreter_target: If set to a target providing a Python binary,
will configure for that Python version instead of the installed
binary. This configuration takes precedence over python_version.
this will configure for that Python version instead of the installed
binary. This configuration takes precedence over python_version. In
addition, setting this label will ignore `PYTHON_LIB_PATH`- if needed
use `python_library_target` to explicitly set the desired library.
python_library_target: If this string is set, this will override the
environmental variable `PYTHON_LIB_PATH`. Otherwise implicit discovery,
is used from the availible binary.
"""