|  | # 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"), | 
|  | }, | 
|  | ) |