blob: 29400be223ac0d5a508c555232d27e80df99ffd6 [file] [log] [blame]
Joshua Haberman5d8c3db2021-08-18 15:16:30 -07001# Copyright (c) 2009-2021, Google LLC
2# All rights reserved.
3#
Joshua Haberman2d6e5542023-09-08 17:13:32 -07004# Use of this source code is governed by a BSD-style
5# license that can be found in the LICENSE file or at
6# https://developers.google.com/open-source/licenses/bsd
Joshua Haberman5d8c3db2021-08-18 15:16:30 -07007
8"""Repository rule for using Python 3.x headers from the system."""
Joshua Haberman94ece042021-08-18 12:38:26 -07009
Mike Kruskal248ed862022-11-30 19:54:51 -080010# Mock out rules_python's pip.bzl for cases where no system python is found.
11_mock_pip = """
12def _pip_install_impl(repository_ctx):
13 repository_ctx.file("BUILD.bazel", '''
14py_library(
15 name = "noop",
16 visibility = ["//visibility:public"],
17)
18''')
19 repository_ctx.file("requirements.bzl", '''
20def install_deps(*args, **kwargs):
21 print("WARNING: could not install pip dependencies")
22
23def requirement(*args, **kwargs):
24 return "@{}//:noop"
25'''.format(repository_ctx.attr.name))
26pip_install = repository_rule(
27 implementation = _pip_install_impl,
28 attrs = {
29 "requirements": attr.string(),
30 "requirements_overrides": attr.string_dict(),
31 "python_interpreter_target": attr.string(),
32 },
33)
34pip_parse = pip_install
35"""
36
Deanna Garcia92dbe4b2022-12-05 11:13:38 -080037# Alias rules_python's pip.bzl for cases where a system python is found.
Mike Kruskal248ed862022-11-30 19:54:51 -080038_alias_pip = """
39load("@rules_python//python:pip.bzl", _pip_install = "pip_install", _pip_parse = "pip_parse")
Deanna Garcia92dbe4b2022-12-05 11:13:38 -080040
Mike Kruskal248ed862022-11-30 19:54:51 -080041def _get_requirements(requirements, requirements_overrides):
42 for version, override in requirements_overrides.items():
43 if version in "{python_version}":
44 requirements = override
45 break
46 return requirements
47
48def pip_install(requirements, requirements_overrides={{}}, **kwargs):
49 _pip_install(
50 python_interpreter_target = "@{repo}//:interpreter",
51 requirements = _get_requirements(requirements, requirements_overrides),
52 **kwargs,
53 )
54def pip_parse(requirements, requirements_overrides={{}}, **kwargs):
55 _pip_parse(
56 python_interpreter_target = "@{repo}//:interpreter",
57 requirements = _get_requirements(requirements, requirements_overrides),
58 **kwargs,
59 )
Mike Kruskalea1996f2022-12-12 10:39:09 -080060"""
61
62_mock_fuzzing_py = """
63def fuzzing_py_install_deps():
64 print("WARNING: could not install fuzzing_py dependencies")
65"""
66
67# Alias rules_fuzzing's requirements.bzl for cases where a system python is found.
68_alias_fuzzing_py = """
69load("@fuzzing_py_deps//:requirements.bzl", _fuzzing_py_install_deps = "install_deps")
Deanna Garcia92dbe4b2022-12-05 11:13:38 -080070
71def fuzzing_py_install_deps():
72 _fuzzing_py_install_deps()
Mike Kruskal248ed862022-11-30 19:54:51 -080073"""
74
Joshua Haberman94ece042021-08-18 12:38:26 -070075_build_file = """
Mike Kruskal248ed862022-11-30 19:54:51 -080076load("@bazel_skylib//lib:selects.bzl", "selects")
77load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
Joshua Haberman71837802021-08-24 07:58:37 -070078load("@bazel_tools//tools/python:toolchain.bzl", "py_runtime_pair")
Protobuf Teamee6b1ab2022-04-13 09:06:17 -070079
Joshua Haberman94ece042021-08-18 12:38:26 -070080cc_library(
81 name = "python_headers",
Adam Cozzette94c4c322023-08-23 06:36:52 -070082 hdrs = glob(["python/**/*.h"], allow_empty = True),
Joshua Haberman94ece042021-08-18 12:38:26 -070083 includes = ["python"],
84 visibility = ["//visibility:public"],
85)
Joshua Haberman71837802021-08-24 07:58:37 -070086
Mike Kruskal248ed862022-11-30 19:54:51 -080087string_flag(
88 name = "internal_python_support",
89 build_setting_default = "{support}",
90 values = [
91 "None",
92 "Supported",
93 "Unsupported",
94 ]
95)
96
97config_setting(
98 name = "none",
99 flag_values = {{
100 ":internal_python_support": "None",
101 }},
102 visibility = ["//visibility:public"],
103)
104
105config_setting(
106 name = "supported",
107 flag_values = {{
108 ":internal_python_support": "Supported",
109 }},
110 visibility = ["//visibility:public"],
111)
112
113config_setting(
114 name = "unsupported",
115 flag_values = {{
116 ":internal_python_support": "Unsupported",
117 }},
118 visibility = ["//visibility:public"],
119)
120
121selects.config_setting_group(
122 name = "exists",
123 match_any = [":supported", ":unsupported"],
124 visibility = ["//visibility:public"],
125)
126
127sh_binary(
128 name = "interpreter",
129 srcs = ["interpreter"],
130 visibility = ["//visibility:public"],
131)
132
Joshua Haberman71837802021-08-24 07:58:37 -0700133py_runtime(
134 name = "py3_runtime",
Mike Kruskal248ed862022-11-30 19:54:51 -0800135 interpreter_path = "{interpreter}",
Joshua Haberman71837802021-08-24 07:58:37 -0700136 python_version = "PY3",
137)
138
139py_runtime_pair(
140 name = "runtime_pair",
141 py3_runtime = ":py3_runtime",
142)
143
144toolchain(
145 name = "python_toolchain",
146 toolchain = ":runtime_pair",
147 toolchain_type = "@rules_python//python:toolchain_type",
148)
Joshua Haberman94ece042021-08-18 12:38:26 -0700149"""
150
Mike Kruskal248ed862022-11-30 19:54:51 -0800151_register = """
152def register_system_python():
153 native.register_toolchains("@{}//:python_toolchain")
154"""
Protobuf Teamee6b1ab2022-04-13 09:06:17 -0700155
Mike Kruskal248ed862022-11-30 19:54:51 -0800156_mock_register = """
157def register_system_python():
158 pass
159"""
160
161def _get_python_version(repository_ctx):
162 py_program = "import sys; print(str(sys.version_info.major) + '.' + str(sys.version_info.minor) + '.' + str(sys.version_info.micro))"
163 result = repository_ctx.execute(["python3", "-c", py_program])
164 return (result.stdout).strip().split(".")
165
166def _get_python_path(repository_ctx):
Joshua Haberman711885d2022-02-26 09:48:05 -0800167 py_program = "import sysconfig; print(sysconfig.get_config_var('%s'), end='')"
Mike Kruskal248ed862022-11-30 19:54:51 -0800168 result = repository_ctx.execute(["python3", "-c", py_program % ("INCLUDEPY")])
Joshua Haberman711885d2022-02-26 09:48:05 -0800169 if result.return_code != 0:
Joshua Haberman7f9135b2022-04-22 15:29:11 -0700170 return None
Joshua Haberman711885d2022-02-26 09:48:05 -0800171 return result.stdout
Joshua Haberman94ece042021-08-18 12:38:26 -0700172
Mike Kruskal248ed862022-11-30 19:54:51 -0800173def _populate_package(ctx, path, python3, python_version):
174 ctx.symlink(path, "python")
Mike Kruskalee564712022-12-02 09:16:31 -0800175 supported = True
Mike Kruskal248ed862022-11-30 19:54:51 -0800176 for idx, v in enumerate(ctx.attr.minimum_python_version.split(".")):
177 if int(python_version[idx]) < int(v):
Mike Kruskalee564712022-12-02 09:16:31 -0800178 supported = False
Mike Kruskal248ed862022-11-30 19:54:51 -0800179 break
Mike Kruskal008a9af2023-02-01 08:04:27 -0800180 if "win" in ctx.os.name:
181 # buildifier: disable=print
182 print("WARNING: python is not supported on Windows")
183 supported = False
Mike Kruskal248ed862022-11-30 19:54:51 -0800184
185 build_file = _build_file.format(
186 interpreter = python3,
Mike Kruskalee564712022-12-02 09:16:31 -0800187 support = "Supported" if supported else "Unsupported",
Mike Kruskal248ed862022-11-30 19:54:51 -0800188 )
189
Vincent Thiberville558c7722023-06-20 08:13:46 -0700190 ctx.file("interpreter", "#!/bin/sh\nexec {} \"$@\"".format(python3))
Mike Kruskal248ed862022-11-30 19:54:51 -0800191 ctx.file("BUILD.bazel", build_file)
192 ctx.file("version.bzl", "SYSTEM_PYTHON_VERSION = '{}{}'".format(python_version[0], python_version[1]))
193 ctx.file("register.bzl", _register.format(ctx.attr.name))
Mike Kruskalee564712022-12-02 09:16:31 -0800194 if supported:
195 ctx.file("pip.bzl", _alias_pip.format(
196 python_version = ".".join(python_version),
197 repo = ctx.attr.name,
198 ))
Mike Kruskalea1996f2022-12-12 10:39:09 -0800199 ctx.file("fuzzing_py.bzl", _alias_fuzzing_py)
Mike Kruskalee564712022-12-02 09:16:31 -0800200 else:
Mike Kruskalea1996f2022-12-12 10:39:09 -0800201 # Dependencies are unlikely to be satisfiable for unsupported versions of python.
Mike Kruskalee564712022-12-02 09:16:31 -0800202 ctx.file("pip.bzl", _mock_pip)
Mike Kruskalea1996f2022-12-12 10:39:09 -0800203 ctx.file("fuzzing_py.bzl", _mock_fuzzing_py)
Mike Kruskal248ed862022-11-30 19:54:51 -0800204
205def _populate_empty_package(ctx):
206 # Mock out all the entrypoints we need to run from WORKSPACE. Targets that
207 # actually need python should use `target_compatible_with` and the generated
208 # @system_python//:exists or @system_python//:supported constraints.
209 ctx.file(
210 "BUILD.bazel",
211 _build_file.format(
212 interpreter = "",
213 support = "None",
214 ),
215 )
216 ctx.file("version.bzl", "SYSTEM_PYTHON_VERSION = 'None'")
217 ctx.file("register.bzl", _mock_register)
218 ctx.file("pip.bzl", _mock_pip)
Mike Kruskalea1996f2022-12-12 10:39:09 -0800219 ctx.file("fuzzing_py.bzl", _mock_fuzzing_py)
Mike Kruskal248ed862022-11-30 19:54:51 -0800220
221def _system_python_impl(repository_ctx):
222 path = _get_python_path(repository_ctx)
Joshua Haberman711885d2022-02-26 09:48:05 -0800223 python3 = repository_ctx.which("python3")
Protobuf Teamee6b1ab2022-04-13 09:06:17 -0700224 python_version = _get_python_version(repository_ctx)
Joshua Haberman94ece042021-08-18 12:38:26 -0700225
Mike Kruskal248ed862022-11-30 19:54:51 -0800226 if path and python_version[0] == "3":
227 _populate_package(repository_ctx, path, python3, python_version)
228 else:
229 # buildifier: disable=print
230 print("WARNING: no system python available, builds against system python will fail")
231 _populate_empty_package(repository_ctx)
232
233# The system_python() repository rule exposes information from the version of python installed in the current system.
Joshua Haberman5d8c3db2021-08-18 15:16:30 -0700234#
235# In WORKSPACE:
Joshua Haberman71837802021-08-24 07:58:37 -0700236# system_python(
237# name = "system_python_repo",
Mike Kruskal248ed862022-11-30 19:54:51 -0800238# minimum_python_version = "3.7",
Joshua Haberman5d8c3db2021-08-18 15:16:30 -0700239# )
240#
Mike Kruskal248ed862022-11-30 19:54:51 -0800241# This repository exposes some repository rules for configuring python in Bazel. The python toolchain
242# *must* be registered in your WORKSPACE:
243# load("@system_python_repo//:register.bzl", "register_system_python")
244# register_system_python()
245#
246# Pip dependencies can optionally be specified using a wrapper around rules_python's repository rules:
247# load("@system_python//:pip.bzl", "pip_install")
248# pip_install(
249# name="pip_deps",
250# requirements = "@com_google_protobuf//python:requirements.txt",
251# )
252# An optional argument `requirements_overrides` takes a dictionary mapping python versions to alternate
253# requirements files. This works around the requirement for fully pinned dependencies in python_rules.
254#
255# Four config settings are exposed from this repository to help declare target compatibility in Bazel.
256# For example, `@system_python_repo//:exists` will be true if a system python version has been found.
257# The `none` setting will be true only if no python version was found, and `supported`/`unsupported`
258# correspond to whether or not the system version is compatible with `minimum_python_version`.
259#
260# This repository also exposes a header rule that you can depend on from BUILD files:
Joshua Haberman5d8c3db2021-08-18 15:16:30 -0700261# cc_library(
262# name = "foobar",
263# srcs = ["foobar.cc"],
Joshua Haberman71837802021-08-24 07:58:37 -0700264# deps = ["@system_python_repo//:python_headers"],
Joshua Haberman5d8c3db2021-08-18 15:16:30 -0700265# )
266#
Joshua Habermanae160982021-08-18 16:45:42 -0700267# The headers should correspond to the version of python obtained by running
268# the `python3` command on the system.
Joshua Haberman71837802021-08-24 07:58:37 -0700269system_python = repository_rule(
Mike Kruskal248ed862022-11-30 19:54:51 -0800270 implementation = _system_python_impl,
Joshua Haberman94ece042021-08-18 12:38:26 -0700271 local = True,
Mike Kruskal248ed862022-11-30 19:54:51 -0800272 attrs = {
273 "minimum_python_version": attr.string(default = "3.7"),
274 },
Joshua Haberman94ece042021-08-18 12:38:26 -0700275)