Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions internal_deps.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ def rules_python_internal_deps():

http_archive(
name = "rules_testing",
sha256 = "8df0a8eb21739ea4b0a03f5dc79e68e245a45c076cfab404b940cc205cb62162",
strip_prefix = "rules_testing-0.4.0",
url = "https://github.com/bazelbuild/rules_testing/releases/download/v0.4.0/rules_testing-v0.4.0.tar.gz",
sha256 = "b84ed8546f1969d700ead4546de9f7637e0f058d835e47e865dcbb13c4210aed",
strip_prefix = "rules_testing-0.5.0",
url = "https://github.com/bazelbuild/rules_testing/releases/download/v0.5.0/rules_testing-v0.5.0.tar.gz",
)

http_archive(
Expand Down
12 changes: 12 additions & 0 deletions python/cc/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")
load("//python/private:current_py_cc_headers.bzl", "current_py_cc_headers")
load("//python/private:current_py_cc_libs.bzl", "current_py_cc_libs")

package(
default_visibility = ["//:__subpackages__"],
Expand All @@ -19,6 +20,17 @@ current_py_cc_headers(
visibility = ["//visibility:public"],
)

# This target provides the C libraries 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_libs(
name = "current_py_cc_libs",
# Building this directly will fail unless a py cc toolchain is registered,
# and it's only under bzlmod that one is registered by default.
tags = [] if BZLMOD_ENABLED else ["manual"],
visibility = ["//visibility:public"],
)

toolchain_type(
name = "toolchain_type",
visibility = ["//visibility:public"],
Expand Down
41 changes: 41 additions & 0 deletions python/private/current_py_cc_libs.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Copyright 2024 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_libs rule."""

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

current_py_cc_libs = rule(
implementation = _current_py_cc_libs_impl,
toolchains = ["//python/cc:toolchain_type"],
provides = [CcInfo],
doc = """\
Provides the currently active Python toolchain's C libraries.

This is a wrapper around the underlying `cc_library()` for the
C libraries for the consuming target's currently active Python toolchain.

To use, simply depend on this target where you would have wanted the
toolchain's underlying `:libpython` target:

```starlark
cc_library(
name = "foo",
deps = ["@rules_python//python/cc:current_py_cc_libs"]
)
```
""",
)
21 changes: 21 additions & 0 deletions python/private/py_cc_toolchain_info.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,27 @@ PyCcToolchainInfo = provider(
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 beginning 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).
""",
"libs": """\
(struct) Information about C libraries, 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 libraries.
* DefaultInfo: the DefaultInfo provider instance for the headers.

A map is used to allow additional providers from the originating libraries
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 beginning with `_` (underscore) are
Expand Down
12 changes: 12 additions & 0 deletions python/private/py_cc_toolchain_rule.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ def _py_cc_toolchain_impl(ctx):
"DefaultInfo": ctx.attr.headers[DefaultInfo],
},
),
libs = struct(
providers_map = {
"CcInfo": ctx.attr.libs[CcInfo],
"DefaultInfo": ctx.attr.libs[DefaultInfo],
},
),
python_version = ctx.attr.python_version,
)
return [platform_common.ToolchainInfo(
Expand All @@ -43,6 +49,12 @@ py_cc_toolchain = rule(
providers = [CcInfo],
mandatory = True,
),
"libs": attr.label(
doc = ("Target that provides the Python runtime libraries for linking. " +
"Typically this is a cc_library target of `.so` files."),
providers = [CcInfo],
mandatory = True,
),
"python_version": attr.string(
doc = "The Major.minor Python version, e.g. 3.11",
mandatory = True,
Expand Down
1 change: 1 addition & 0 deletions python/repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ py_runtime_pair(
py_cc_toolchain(
name = "py_cc_toolchain",
headers = ":python_headers",
libs = ":libpython",
python_version = "{python_version}",
)
""".format(
Expand Down
15 changes: 15 additions & 0 deletions tests/cc/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ package(default_visibility = ["//:__subpackages__"])

exports_files(["fake_header.h"])

filegroup(
name = "libpython",
srcs = ["libpython-fake.so"],
tags = PREVENT_IMPLICIT_BUILDING_TAGS,
)

toolchain(
name = "fake_py_cc_toolchain",
tags = PREVENT_IMPLICIT_BUILDING_TAGS,
Expand All @@ -31,6 +37,7 @@ toolchain(
py_cc_toolchain(
name = "fake_py_cc_toolchain_impl",
headers = ":fake_headers",
libs = ":fake_libs",
python_version = "3.999",
tags = PREVENT_IMPLICIT_BUILDING_TAGS,
)
Expand All @@ -44,6 +51,14 @@ cc_library(
tags = PREVENT_IMPLICIT_BUILDING_TAGS,
)

# buildifier: disable=native-cc
cc_library(
name = "fake_libs",
srcs = ["libpython3.so"],
data = ["libdata.txt"],
tags = PREVENT_IMPLICIT_BUILDING_TAGS,
)

cc_toolchain_suite(
name = "cc_toolchain_suite",
tags = ["manual"],
Expand Down
17 changes: 17 additions & 0 deletions tests/cc/current_py_cc_libs/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright 2024 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_libs_tests.bzl", "current_py_cc_libs_test_suite")

current_py_cc_libs_test_suite(name = "current_py_cc_libs_tests")
77 changes: 77 additions & 0 deletions tests/cc/current_py_cc_libs/current_py_cc_libs_tests.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Copyright 2024 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_libs."""

load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite")
load("@rules_testing//lib:truth.bzl", "matching")
load("//tests:cc_info_subject.bzl", "cc_info_subject")

_tests = []

def _test_current_toolchain_libs(name):
analysis_test(
name = name,
impl = _test_current_toolchain_libs_impl,
target = "//python/cc:current_py_cc_libs",
config_settings = {
"//command_line_option:extra_toolchains": [str(Label("//tests/cc:all"))],
},
attrs = {
"lib": attr.label(
default = "//tests/cc:libpython",
allow_single_file = True,
),
},
)

def _test_current_toolchain_libs_impl(env, target):
# Check that the forwarded CcInfo looks vaguely correct.
cc_info = env.expect.that_target(target).provider(
CcInfo,
factory = cc_info_subject,
)
cc_info.linking_context().linker_inputs().has_size(2)

# Check that the forward DefaultInfo looks correct
env.expect.that_target(target).runfiles().contains_predicate(
matching.str_matches("*/libdata.txt"),
)

# The shared library should also end up in runfiles
# The `_solib` directory is a special directory CC rules put
# libraries into.
env.expect.that_target(target).runfiles().contains_predicate(
matching.str_matches("*_solib*/libpython3.so"),
)

_tests.append(_test_current_toolchain_libs)

def _test_toolchain_is_registered_by_default(name):
analysis_test(
name = name,
impl = _test_toolchain_is_registered_by_default_impl,
target = "//python/cc:current_py_cc_libs",
)

def _test_toolchain_is_registered_by_default_impl(env, target):
env.expect.that_target(target).has_provider(CcInfo)

_tests.append(_test_toolchain_is_registered_by_default)

def current_py_cc_libs_test_suite(name):
test_suite(
name = name,
tests = _tests,
)
15 changes: 14 additions & 1 deletion tests/cc/py_cc_toolchain/py_cc_toolchain_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"""Tests for py_cc_toolchain."""

load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite")
load("@rules_testing//lib:truth.bzl", "matching")
load("@rules_testing//lib:truth.bzl", "matching", "subjects")
load("//tests:cc_info_subject.bzl", "cc_info_subject")
load("//tests:default_info_subject.bzl", "default_info_subject")
load("//tests:py_cc_toolchain_info_subject.bzl", "PyCcToolchainInfoSubject")
Expand Down Expand Up @@ -74,6 +74,19 @@ def _py_cc_toolchain_test_impl(env, target):
matching.str_matches("*/cc/data.txt"),
)

libs_providers = toolchain.libs().providers_map()
libs_providers.keys().contains_exactly(["CcInfo", "DefaultInfo"])

cc_info = libs_providers.get("CcInfo", factory = cc_info_subject)

cc_info.linking_context().linker_inputs().has_size(2)

default_info = libs_providers.get("DefaultInfo", factory = subjects.default_info)
default_info.runfiles().contains("{workspace}/tests/cc/libdata.txt")
default_info.runfiles().contains_predicate(
matching.str_matches("/libpython3."),
)

_tests.append(_py_cc_toolchain_test)

def py_cc_toolchain_test_suite(name):
Expand Down
55 changes: 55 additions & 0 deletions tests/cc_info_subject.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ def cc_info_subject(info, *, meta):
# buildifier: disable=uninitialized
public = struct(
# go/keep-sorted start
actual = info,
compilation_context = lambda *a, **k: _cc_info_subject_compilation_context(self, *a, **k),
linking_context = lambda *a, **k: _cc_info_subject_linking_context(self, *a, **k),
# go/keep-sorted end
)
self = struct(
Expand All @@ -52,6 +54,20 @@ def _cc_info_subject_compilation_context(self):
meta = self.meta.derive("compilation_context()"),
)

def _cc_info_subject_linking_context(self):
"""Returns the CcInfo.linking_context as a subject.

Args:
self: implicitly added.

Returns:
[`LinkingContextSubject`] instance.
"""
return _linking_context_subject_new(
self.actual.linking_context,
meta = self.meta.derive("linking_context()"),
)

def _compilation_context_subject_new(info, *, meta):
"""Creates a CompilationContextSubject.

Expand Down Expand Up @@ -126,3 +142,42 @@ def _compilation_context_subject_system_includes(self):
container_name = "includes",
element_plural_name = "include paths",
)

def _linking_context_subject_new(info, meta):
"""Creates a LinkingContextSubject.

Args:
info: ([`LinkingContext`]) object instance.
meta: rules_testing `ExpectMeta` instance.

Returns:
[`LinkingContextSubject`] object.
"""

# buildifier: disable=uninitialized
public = struct(
# go/keep-sorted start
linker_inputs = lambda *a, **k: _linking_context_subject_linker_inputs(self, *a, **k),
# go/keep-sorted end
)
self = struct(
actual = info,
meta = meta,
)
return public

def _linking_context_subject_linker_inputs(self):
"""Returns the linker inputs.

Args:
self: implicitly added

Returns:
[`CollectionSubject`] of the linker inputs.
"""
return subjects.collection(
self.actual.linker_inputs.to_list(),
meta = self.meta.derive("linker_inputs()"),
container_name = "linker_inputs",
element_plural_name = "linker input values",
)
Loading