Skip to content

Commit

Permalink
feat: Expose Python C headers through the toolchain.
Browse files Browse the repository at this point in the history
Tihs allows getting a build's `cc_library` of Python headers through
toolchain resolution instead of having to use the underlying toolchain's
repository `:python_headers` target directly. This makes it easier and
more reliable to build C extensions with the correct Python runtime's
headers (e.g. the same ones for the runtime a binary uses).

Work towards bazelbuild#824
  • Loading branch information
rickeylev committed Jun 29, 2023
1 parent 5b8fa22 commit 8a3cc22
Show file tree
Hide file tree
Showing 19 changed files with 714 additions and 4 deletions.
16 changes: 16 additions & 0 deletions python/cc/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Package for C/C++ specific functionality of the Python rules.

load("//python/private:current_py_cc_headers.bzl", "current_py_cc_headers")

# This target provides the C headers for whatever the current toolchain is
# for the consuming rule. It basically acts like a cc_library by forwarding
# on the providers for the underlying cc_library that the toolchain is using.
current_py_cc_headers(
name = "current_py_cc_headers",
visibility = ["//visibility:public"],
)

toolchain_type(
name = "toolchain_type",
visibility = ["//visibility:public"],
)
19 changes: 19 additions & 0 deletions python/cc/py_cc_toolchain.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2023 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Public entry point for py_cc_toolchain rule."""

load("//python/private:py_cc_toolchain_macro.bzl", _py_cc_toolchain = "py_cc_toolchain")

py_cc_toolchain = _py_cc_toolchain
19 changes: 19 additions & 0 deletions python/cc/py_cc_toolchain_info.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2023 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Public entry point for PyCcToolchainInfo."""

load("//python/private:py_cc_toolchain_info.bzl", _PyCcToolchainInfo = "PyCcToolchainInfo")

PyCcToolchainInfo = _PyCcToolchainInfo
25 changes: 25 additions & 0 deletions python/private/current_py_cc_headers.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2023 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Implementation of current_py_cc_headers rule."""

def _current_py_cc_headers_impl(ctx):
py_cc_toolchain = ctx.toolchains["//python/cc:toolchain_type"].py_cc_toolchain
return py_cc_toolchain.headers.providers_map.values()

current_py_cc_headers = rule(
implementation = _current_py_cc_headers_impl,
toolchains = ["//python/cc:toolchain_type"],
provides = [CcInfo],
)
43 changes: 43 additions & 0 deletions python/private/py_cc_toolchain_info.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright 2023 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Implementation of PyCcToolchainInfo."""

PyCcToolchainInfo = provider(
doc = "C/C++ information about the Python runtime.",
fields = {
"headers": """\
(struct) Information about the header files, with fields:
* providers_map: a dict of string to provider instances. The key should be
a fully qualified name (e.g. `@rules_foo//bar:baz.bzl#MyInfo`) of the
provider to uniquely identify its type.
The following keys are always present:
* CcInfo: the CcInfo provider instance for the headers.
* DefaultInfo: the DefaultInfo provider instance for the headers.
A map is used to allow additional providers from the originating headers
target (typically a `cc_library`) to be propagated to consumers (directly
exposing a Target object can cause memory issues and is an anti-pattern).
When consuming this map, it's suggested to use `providers_map.values()` to
return all providers; or copy the map and filter out or replace keys as
appropriate. Note that any keys begining with `_` (underscore) are
considered private and should be forward along as-is (this better allows
e.g. `:current_py_cc_headers` to act as the underlying headers target it
represents).
""",
"python_version": "(str) The Python Major.Minor version.",
},
)
25 changes: 25 additions & 0 deletions python/private/py_cc_toolchain_macro.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2023 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Fronting macro for the py_cc_toolchain rule."""

load(":py_cc_toolchain_rule.bzl", _py_cc_toolchain = "py_cc_toolchain")
load(":util.bzl", "add_tag")

# A fronting macro is used because macros have user-observable behavior;
# using one from the onset avoids introducing those changes in the future.
def py_cc_toolchain(**kwargs):
# This tag is added to easily identify usages through other macros.
add_tag(kwargs, "@rules_python//python:py_cc_toolchain")
_py_cc_toolchain(**kwargs)
47 changes: 47 additions & 0 deletions python/private/py_cc_toolchain_rule.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright 2023 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Implementation of py_cc_toolchain rule."""

load(":py_cc_toolchain_info.bzl", "PyCcToolchainInfo")

def _py_cc_toolchain_impl(ctx):
py_cc_toolchain = PyCcToolchainInfo(
headers = struct(
providers_map = {
"CcInfo": ctx.attr.headers[CcInfo],
"DefaultInfo": ctx.attr.headers[DefaultInfo],
},
),
python_version = ctx.attr.python_version,
)
return [platform_common.ToolchainInfo(
py_cc_toolchain = py_cc_toolchain,
)]

py_cc_toolchain = rule(
implementation = _py_cc_toolchain_impl,
attrs = {
"headers": attr.label(
doc = ("Target that provides the Python headers. Typically this " +
"is a cc_library target."),
providers = [CcInfo],
mandatory = True,
),
"python_version": attr.string(
doc = "The Major.minor Python version, e.g. 3.11",
mandatory = True,
),
},
)
9 changes: 9 additions & 0 deletions python/private/toolchains_repo.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,15 @@ toolchain(
toolchain = "@{user_repository_name}_{platform}//:python_runtimes",
toolchain_type = "@bazel_tools//tools/python:toolchain_type",
)
toolchain(
name = "{prefix}{platform}_py_cc_toolchain",
target_compatible_with = {compatible_with},
target_settings = {target_settings},
toolchain = "@{user_repository_name}_{platform}//:py_cc_toolchain",
toolchain_type = "@rules_python//python/cc:toolchain_type",
)
""".format(
compatible_with = meta.compatible_with,
platform = platform,
Expand Down
27 changes: 23 additions & 4 deletions python/private/util.bzl
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# Copyright 2023 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Functionality shared by multiple pieces of code."""

load("@bazel_skylib//lib:types.bzl", "types")
Expand Down Expand Up @@ -46,15 +60,20 @@ def add_migration_tag(attrs):
Returns:
The same `attrs` object, but modified.
"""
add_tag(attrs, _MIGRATION_TAG)
return attrs

def add_tag(attrs, tag):
if "tags" in attrs and attrs["tags"] != None:
tags = attrs["tags"]

# Preserve the input type: this allows a test verifying the underlying
# rule can accept the tuple for the tags argument.
if types.is_tuple(tags):
attrs["tags"] = tags + (_MIGRATION_TAG,)
attrs["tags"] = tags + (tag,)
else:
attrs["tags"] = tags + [_MIGRATION_TAG]
# List concatenation is necessary because the original value
# may be a frozen list.
attrs["tags"] = tags + [tag]
else:
attrs["tags"] = [_MIGRATION_TAG]
return attrs
attrs["tags"] = [tag]
7 changes: 7 additions & 0 deletions python/repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ def _python_repository_impl(rctx):
# Generated by python/repositories.bzl
load("@bazel_tools//tools/python:toolchain.bzl", "py_runtime_pair")
load("@rules_python//python/cc:py_cc_toolchain.bzl", "py_cc_toolchain")
package(default_visibility = ["//visibility:public"])
Expand Down Expand Up @@ -336,6 +337,12 @@ py_runtime_pair(
py2_runtime = None,
py3_runtime = ":py3_runtime",
)
py_cc_toolchain(
name = "py_cc_toolchain",
headers = ":python_headers",
python_version = "{python_version}",
)
""".format(
glob_exclude = repr(glob_exclude),
glob_include = repr(glob_include),
Expand Down
44 changes: 44 additions & 0 deletions tests/cc/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Copyright 2023 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Tests for current_py_cc_headers rule

load("//python/cc:py_cc_toolchain.bzl", "py_cc_toolchain")
load("@rules_testing//lib:util.bzl", "PREVENT_IMPLICIT_BUILDING_TAGS")

package(default_visibility = ["//:__subpackages__"])

toolchain(
name = "fake_py_cc_toolchain",
tags = PREVENT_IMPLICIT_BUILDING_TAGS,
toolchain = ":fake_py_cc_toolchain_impl",
toolchain_type = "@rules_python//python/cc:toolchain_type",
)

py_cc_toolchain(
name = "fake_py_cc_toolchain_impl",
testonly = True,
headers = ":fake_headers",
python_version = "3.999",
tags = PREVENT_IMPLICIT_BUILDING_TAGS,
)

cc_library(
name = "fake_headers",
testonly = True,
hdrs = ["fake_header.h"],
data = ["data.txt"],
includes = ["fake_include"],
tags = PREVENT_IMPLICIT_BUILDING_TAGS,
)
17 changes: 17 additions & 0 deletions tests/cc/current_py_cc_headers/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright 2023 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

load(":current_py_cc_headers_tests.bzl", "current_py_cc_headers_test_suite")

current_py_cc_headers_test_suite(name = "current_py_cc_headers_tests")
Loading

0 comments on commit 8a3cc22

Please sign in to comment.