@ -25,7 +25,60 @@
""" Repository rule for using Python 3.x headers from the system. """
# Mock out rules_python's pip.bzl for cases where no system python is found.
_mock_pip = """
def _pip_install_impl ( repository_ctx ) :
repository_ctx . file ( " BUILD.bazel " , '''
py_library (
name = " noop " ,
visibility = [ " //visibility:public " ] ,
)
''' )
repository_ctx . file ( " requirements.bzl " , '''
def install_deps ( * args , * * kwargs ) :
print ( " WARNING: could not install pip dependencies " )
def requirement ( * args , * * kwargs ) :
return " @ {} //:noop "
''' .format(repository_ctx.attr.name))
pip_install = repository_rule (
implementation = _pip_install_impl ,
attrs = {
" requirements " : attr . string ( ) ,
" requirements_overrides " : attr . string_dict ( ) ,
" python_interpreter_target " : attr . string ( ) ,
} ,
)
pip_parse = pip_install
"""
# Alias rules_python's pip.bzl for cases where a system pythong is found.
_alias_pip = """
load ( " @rules_python//python:pip.bzl " , _pip_install = " pip_install " , _pip_parse = " pip_parse " )
def _get_requirements ( requirements , requirements_overrides ) :
for version , override in requirements_overrides . items ( ) :
if version in " {python_version} " :
requirements = override
break
return requirements
def pip_install ( requirements , requirements_overrides = { { } } , * * kwargs ) :
_pip_install (
python_interpreter_target = " @ {repo} //:interpreter " ,
requirements = _get_requirements ( requirements , requirements_overrides ) ,
* * kwargs ,
)
def pip_parse ( requirements , requirements_overrides = { { } } , * * kwargs ) :
_pip_parse (
python_interpreter_target = " @ {repo} //:interpreter " ,
requirements = _get_requirements ( requirements , requirements_overrides ) ,
* * kwargs ,
)
"""
_build_file = """
load ( " @bazel_skylib//lib:selects.bzl " , " selects " )
load ( " @bazel_skylib//rules:common_settings.bzl " , " string_flag " )
load ( " @bazel_tools//tools/python:toolchain.bzl " , " py_runtime_pair " )
cc_library (
@ -35,9 +88,55 @@ cc_library(
visibility = [ " //visibility:public " ] ,
)
string_flag (
name = " internal_python_support " ,
build_setting_default = " {support} " ,
values = [
" None " ,
" Supported " ,
" Unsupported " ,
]
)
config_setting (
name = " none " ,
flag_values = { {
" :internal_python_support " : " None " ,
} } ,
visibility = [ " //visibility:public " ] ,
)
config_setting (
name = " supported " ,
flag_values = { {
" :internal_python_support " : " Supported " ,
} } ,
visibility = [ " //visibility:public " ] ,
)
config_setting (
name = " unsupported " ,
flag_values = { {
" :internal_python_support " : " Unsupported " ,
} } ,
visibility = [ " //visibility:public " ] ,
)
selects . config_setting_group (
name = " exists " ,
match_any = [ " :supported " , " :unsupported " ] ,
visibility = [ " //visibility:public " ] ,
)
sh_binary (
name = " interpreter " ,
srcs = [ " interpreter " ] ,
visibility = [ " //visibility:public " ] ,
)
py_runtime (
name = " py3_runtime " ,
interpreter_path = " {} " ,
interpreter_path = " {interpreter } " ,
python_version = " PY3 " ,
)
@ -53,40 +152,105 @@ toolchain(
)
"""
_register = """
def register_system_python ( ) :
native . register_toolchains ( " @ {} //:python_toolchain " )
"""
_mock_register = """
def register_system_python ( ) :
pass
"""
def _get_python_version ( repository_ctx ) :
py_program = " import sys; print(str(sys.version_info.major) + str(sys.version_info.minor)) "
py_program = " import sys; print(str(sys.version_info.major) + ' . ' + str(sys.version_info.minor) + ' . ' + str(sys.version_info.micro ))"
result = repository_ctx . execute ( [ " python3 " , " -c " , py_program ] )
return ( result . stdout ) . strip ( )
return ( result . stdout ) . strip ( ) . split ( " . " )
def _get_config_var ( repository_ctx , name ) :
def _get_python_path ( repository_ctx ) :
py_program = " import sysconfig; print(sysconfig.get_config_var( ' %s ' ), end= ' ' ) "
result = repository_ctx . execute ( [ " python3 " , " -c " , py_program % ( name ) ] )
result = repository_ctx . execute ( [ " python3 " , " -c " , py_program % ( " INCLUDEPY " ) ] )
if result . return_code != 0 :
return None
return result . stdout
def _python_headers_impl ( repository_ctx ) :
path = _get_config_var ( repository_ctx , " INCLUDEPY " )
if not path :
# buildifier: disable=print
print ( " WARNING: no system python available, builds against system python will fail " )
repository_ctx . file ( " BUILD.bazel " , " " )
repository_ctx . file ( " version.bzl " , " SYSTEM_PYTHON_VERSION = None " )
return
repository_ctx . symlink ( path , " python " )
def _populate_package ( ctx , path , python3 , python_version ) :
ctx . symlink ( path , " python " )
support = " Supported "
for idx , v in enumerate ( ctx . attr . minimum_python_version . split ( " . " ) ) :
if int ( python_version [ idx ] ) < int ( v ) :
support = " Unsupported "
break
build_file = _build_file . format (
interpreter = python3 ,
support = support ,
)
ctx . file ( " interpreter " , " exec {} \" $@ \" " . format ( python3 ) )
ctx . file ( " BUILD.bazel " , build_file )
ctx . file ( " version.bzl " , " SYSTEM_PYTHON_VERSION = ' {} {} ' " . format ( python_version [ 0 ] , python_version [ 1 ] ) )
ctx . file ( " register.bzl " , _register . format ( ctx . attr . name ) )
ctx . file ( " pip.bzl " , _alias_pip . format (
python_version = " . " . join ( python_version ) ,
repo = ctx . attr . name ,
) )
def _populate_empty_package ( ctx ) :
# Mock out all the entrypoints we need to run from WORKSPACE. Targets that
# actually need python should use `target_compatible_with` and the generated
# @system_python//:exists or @system_python//:supported constraints.
ctx . file (
" BUILD.bazel " ,
_build_file . format (
interpreter = " " ,
support = " None " ,
) ,
)
ctx . file ( " version.bzl " , " SYSTEM_PYTHON_VERSION = ' None ' " )
ctx . file ( " register.bzl " , _mock_register )
ctx . file ( " pip.bzl " , _mock_pip )
def _system_python_impl ( repository_ctx ) :
path = _get_python_path ( repository_ctx )
python3 = repository_ctx . which ( " python3 " )
python_version = _get_python_version ( repository_ctx )
repository_ctx . file ( " BUILD.bazel " , _build_file . format ( python3 ) )
repository_ctx . file ( " version.bzl " , " SYSTEM_PYTHON_VERSION = ' {} ' " . format ( python_version ) )
# The system_python() repository rule exposes Python headers from the system.
if path and python_version [ 0 ] == " 3 " :
_populate_package ( repository_ctx , path , python3 , python_version )
else :
# buildifier: disable=print
print ( " WARNING: no system python available, builds against system python will fail " )
_populate_empty_package ( repository_ctx )
# The system_python() repository rule exposes information from the version of python installed in the current system.
#
# In WORKSPACE:
# system_python(
# name = "system_python_repo",
# minimum_python_version = "3.7",
# )
#
# This repository exposes a single rule that you can depend on from BUILD:
# This repository exposes some repository rules for configuring python in Bazel. The python toolchain
# *must* be registered in your WORKSPACE:
# load("@system_python_repo//:register.bzl", "register_system_python")
# register_system_python()
#
# Pip dependencies can optionally be specified using a wrapper around rules_python's repository rules:
# load("@system_python//:pip.bzl", "pip_install")
# pip_install(
# name="pip_deps",
# requirements = "@com_google_protobuf//python:requirements.txt",
# )
# An optional argument `requirements_overrides` takes a dictionary mapping python versions to alternate
# requirements files. This works around the requirement for fully pinned dependencies in python_rules.
#
# Four config settings are exposed from this repository to help declare target compatibility in Bazel.
# For example, `@system_python_repo//:exists` will be true if a system python version has been found.
# The `none` setting will be true only if no python version was found, and `supported`/`unsupported`
# correspond to whether or not the system version is compatible with `minimum_python_version`.
#
# This repository also exposes a header rule that you can depend on from BUILD files:
# cc_library(
# name = "foobar",
# srcs = ["foobar.cc"],
@ -96,6 +260,9 @@ def _python_headers_impl(repository_ctx):
# The headers should correspond to the version of python obtained by running
# the `python3` command on the system.
system_python = repository_rule (
implementation = _python_headers _impl ,
implementation = _system_ python_impl ,
local = True ,
attrs = {
" minimum_python_version " : attr . string ( default = " 3.7 " ) ,
} ,
)