| # Copyright (c) 2009-2021, Google LLC |
| # All rights reserved. |
| # |
| # Use of this source code is governed by a BSD-style |
| # license that can be found in the LICENSE file or at |
| # https://developers.google.com/open-source/licenses/bsd |
| |
| """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 python 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, |
| ) |
| """ |
| |
| _mock_fuzzing_py = """ |
| def fuzzing_py_install_deps(): |
| print("WARNING: could not install fuzzing_py dependencies") |
| """ |
| |
| # Alias rules_fuzzing's requirements.bzl for cases where a system python is found. |
| _alias_fuzzing_py = """ |
| load("@fuzzing_py_deps//:requirements.bzl", _fuzzing_py_install_deps = "install_deps") |
| |
| def fuzzing_py_install_deps(): |
| _fuzzing_py_install_deps() |
| """ |
| |
| _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( |
| name = "python_headers", |
| hdrs = glob(["python/**/*.h"], allow_empty = True), |
| includes = ["python"], |
| 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}", |
| python_version = "PY3", |
| ) |
| |
| py_runtime_pair( |
| name = "runtime_pair", |
| py3_runtime = ":py3_runtime", |
| ) |
| |
| toolchain( |
| name = "python_toolchain", |
| toolchain = ":runtime_pair", |
| toolchain_type = "@rules_python//python:toolchain_type", |
| ) |
| """ |
| |
| _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) + '.' + str(sys.version_info.micro))" |
| result = repository_ctx.execute(["python3", "-c", py_program]) |
| return (result.stdout).strip().split(".") |
| |
| 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 % ("INCLUDEPY")]) |
| if result.return_code != 0: |
| return None |
| return result.stdout |
| |
| def _populate_package(ctx, path, python3, python_version): |
| ctx.symlink(path, "python") |
| supported = True |
| for idx, v in enumerate(ctx.attr.minimum_python_version.split(".")): |
| if int(python_version[idx]) < int(v): |
| supported = False |
| break |
| if "win" in ctx.os.name: |
| # buildifier: disable=print |
| print("WARNING: python is not supported on Windows") |
| supported = False |
| |
| build_file = _build_file.format( |
| interpreter = python3, |
| support = "Supported" if supported else "Unsupported", |
| ) |
| |
| ctx.file("interpreter", "#!/bin/sh\nexec {} \"$@\"".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)) |
| if supported: |
| ctx.file("pip.bzl", _alias_pip.format( |
| python_version = ".".join(python_version), |
| repo = ctx.attr.name, |
| )) |
| ctx.file("fuzzing_py.bzl", _alias_fuzzing_py) |
| else: |
| # Dependencies are unlikely to be satisfiable for unsupported versions of python. |
| ctx.file("pip.bzl", _mock_pip) |
| ctx.file("fuzzing_py.bzl", _mock_fuzzing_py) |
| |
| 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) |
| ctx.file("fuzzing_py.bzl", _mock_fuzzing_py) |
| |
| def _system_python_impl(repository_ctx): |
| path = _get_python_path(repository_ctx) |
| python3 = repository_ctx.which("python3") |
| python_version = _get_python_version(repository_ctx) |
| |
| 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 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"], |
| # deps = ["@system_python_repo//:python_headers"], |
| # ) |
| # |
| # The headers should correspond to the version of python obtained by running |
| # the `python3` command on the system. |
| system_python = repository_rule( |
| implementation = _system_python_impl, |
| local = True, |
| attrs = { |
| "minimum_python_version": attr.string(default = "3.7"), |
| }, |
| ) |