| # 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_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_parse(requirements, requirements_overrides={{}}, **kwargs): | |
| _pip_parse( | |
| python_interpreter_target = "@{repo}//:interpreter", | |
| requirements_lock = _get_requirements(requirements, requirements_overrides), | |
| **kwargs, | |
| ) | |
| pip_install = pip_parse | |
| """ | |
| _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("@rules_shell//shell:sh_binary.bzl", "sh_binary") | |
| load("@rules_cc//cc:defs.bzl", "cc_library") | |
| load("@bazel_skylib//lib:selects.bzl", "selects") | |
| load("@bazel_skylib//rules:common_settings.bzl", "string_flag") | |
| load("@rules_python//python:py_runtime.bzl", "py_runtime") | |
| load("@rules_python//python:py_runtime_pair.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="protobuf_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.10"), | |
| }, | |
| ) | |
| def _system_python_extension(ctx): | |
| for mod in ctx.modules: | |
| for py in mod.tags.find: | |
| system_python( | |
| name = py.name, | |
| minimum_python_version = py.minimum, | |
| ) | |
| find = tag_class(attrs = { | |
| "name": attr.string(doc = "Supported versions of python to find"), | |
| "minimum": attr.string(), | |
| }) | |
| system_python_extension = module_extension( | |
| implementation = _system_python_extension, | |
| tag_classes = { | |
| "find": find, | |
| }, | |
| ) |