Skip to content

Commit 024e62c

Browse files
authored
Allow PyInfo in mojo_binary / mojo_test deps (#3)
This enables python interop in the way you'd expect. I don't know if we need this on `mojo_library` ever. The key here was to make sure all DefaultInfo.default_runfiles from the python targets was included, because PyInfo.transitive_files doesn't include native libraries that the dependency vendors
1 parent a8544e2 commit 024e62c

File tree

7 files changed

+134
-4
lines changed

7 files changed

+134
-4
lines changed

.bazelrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
common --incompatible_strict_action_env
2+
common --test_output=errors

MODULE.bazel

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,27 @@ module(
77
bazel_dep(name = "bazel_skylib", version = "1.7.1")
88
bazel_dep(name = "platforms", version = "0.0.11")
99
bazel_dep(name = "rules_cc", version = "0.1.1")
10+
bazel_dep(name = "rules_python", version = "1.0.0")
1011

1112
mojo = use_extension("//mojo:extensions.bzl", "mojo")
1213
mojo.toolchain()
1314
use_repo(mojo, "mojo_toolchains")
1415

1516
register_toolchains("@mojo_toolchains//...")
17+
18+
_DEFAULT_PYTHON_VERSION = "3.12"
19+
20+
python = use_extension("@rules_python//python/extensions:python.bzl", "python", dev_dependency = True)
21+
python.toolchain(
22+
ignore_root_user_error = True,
23+
is_default = True,
24+
python_version = _DEFAULT_PYTHON_VERSION,
25+
)
26+
27+
pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip", dev_dependency = True)
28+
pip.parse(
29+
hub_name = "rules_mojo_test_deps",
30+
python_version = _DEFAULT_PYTHON_VERSION,
31+
requirements_lock = "tests/python/requirements.txt",
32+
)
33+
use_repo(pip, "rules_mojo_test_deps")

mojo/private/mojo_binary_test.bzl

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
load("@bazel_skylib//lib:paths.bzl", "paths")
44
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
55
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain", "use_cpp_toolchain")
6+
load("@rules_python//python:py_info.bzl", "PyInfo")
67
load("//mojo:providers.bzl", "MojoInfo")
78
load(":utils.bzl", "MOJO_EXTENSIONS", "collect_mojoinfo")
89

@@ -16,7 +17,7 @@ _ATTRS = {
1617
),
1718
"copts": attr.string_list(),
1819
"deps": attr.label_list(
19-
providers = [[CcInfo], [MojoInfo]],
20+
providers = [[CcInfo], [MojoInfo], [PyInfo]],
2021
),
2122
"data": attr.label_list(allow_files = True),
2223
"enable_assertions": attr.bool(default = True),
@@ -28,6 +29,7 @@ _ATTRS = {
2829

2930
_TOOLCHAINS = use_cpp_toolchain() + [
3031
"//:toolchain_type",
32+
"@bazel_tools//tools/python:toolchain_type",
3133
]
3234

3335
def _find_main(name, srcs, main):
@@ -56,8 +58,9 @@ def _find_main(name, srcs, main):
5658
fail("Multiple Mojo files provided, but no main file specified. Please set 'main = \"foo.mojo\"' to disambiguate.")
5759

5860
def _mojo_binary_test_implementation(ctx):
59-
mojo_toolchain = ctx.toolchains["//:toolchain_type"].mojo_toolchain_info
6061
cc_toolchain = find_cpp_toolchain(ctx)
62+
mojo_toolchain = ctx.toolchains["//:toolchain_type"].mojo_toolchain_info
63+
py_toolchain = ctx.toolchains["@bazel_tools//tools/python:toolchain_type"]
6164

6265
object_file = ctx.actions.declare_file(ctx.label.name + ".lo")
6366
args = ctx.actions.args()
@@ -138,20 +141,53 @@ def _mojo_binary_test_implementation(ctx):
138141

139142
data = ctx.attr.data
140143
runfiles = ctx.runfiles(ctx.files.data)
141-
transitive_runfiles = []
144+
transitive_runfiles = [
145+
ctx.runfiles(transitive_files = py_toolchain.py3_runtime.files),
146+
]
142147
for target in data:
143148
transitive_runfiles.append(target[DefaultInfo].default_runfiles)
144149

145150
# Collect transitive shared libraries that must exist at runtime
151+
python_imports = []
146152
for target in ctx.attr.deps + mojo_toolchain.implicit_deps:
153+
transitive_runfiles.append(target[DefaultInfo].default_runfiles)
154+
155+
if PyInfo in target:
156+
python_imports.append(target[PyInfo].imports)
157+
transitive_runfiles.append(
158+
ctx.runfiles(transitive_files = target[PyInfo].transitive_sources),
159+
)
160+
147161
if CcInfo not in target:
148162
continue
149163
for linker_input in target[CcInfo].linking_context.linker_inputs.to_list():
150164
for library in linker_input.libraries:
151165
if library.dynamic_library and not library.pic_static_library and not library.static_library:
152166
transitive_runfiles.append(ctx.runfiles(transitive_files = depset([library.dynamic_library])))
153167

154-
runtime_env = dict(ctx.attr.env)
168+
python_path = ""
169+
for path in depset(transitive = python_imports).to_list():
170+
python_path += "../" + path + ":"
171+
172+
# https://github.com/bazelbuild/rules_python/issues/2262
173+
libpython = None
174+
for file in py_toolchain.py3_runtime.files.to_list():
175+
if file.basename.startswith("libpython"):
176+
libpython = file.short_path
177+
break # if there are multiple any of them should work and they are likely symlinks to each other
178+
179+
if not libpython:
180+
fail("failed to find libpython, please report this at https://github.com/modular/rules_mojo/issues")
181+
182+
runtime_env = dict(ctx.attr.env) | {
183+
"MODULAR_PYTHON_EXECUTABLE": py_toolchain.py3_runtime.interpreter.short_path,
184+
"MOJO_PYTHON": py_toolchain.py3_runtime.interpreter.short_path,
185+
"MOJO_PYTHON_LIBRARY": libpython,
186+
"PYTHONEXECUTABLE": py_toolchain.py3_runtime.interpreter.short_path,
187+
"PYTHONNOUSERSITE": "affirmative",
188+
"PYTHONPATH": python_path,
189+
"PYTHONSAFEPATH": "affirmative",
190+
}
155191
for key, value in runtime_env.items():
156192
runtime_env[key] = ctx.expand_make_variables(
157193
"env",

tests/python/BUILD.bazel

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
load("@rules_mojo_test_deps//:requirements.bzl", "requirement")
2+
load("//mojo:mojo_test.bzl", "mojo_test")
3+
4+
mojo_test(
5+
name = "basic_python_test",
6+
srcs = ["basic_python_test.mojo"],
7+
)
8+
9+
mojo_test(
10+
name = "deps_python_test",
11+
srcs = ["deps_python_test.mojo"],
12+
deps = [
13+
requirement("numpy"),
14+
],
15+
)

tests/python/basic_python_test.mojo

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from python import Python
2+
from testing import assert_true
3+
4+
def main():
5+
sys = Python.import_module("sys")
6+
print("Python executable:", sys.executable)
7+
assert_true(not sys.executable.startswith("/usr"))

tests/python/deps_python_test.mojo

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from python import Python
2+
from testing import assert_equal
3+
import os
4+
import subprocess
5+
6+
def test_basic_numpy_example():
7+
var np = Python.import_module("numpy")
8+
var array = np.array(
9+
Python.list(
10+
Python.list(1, 2, 3),
11+
Python.list(4, 5, 6)
12+
)
13+
)
14+
assert_equal(array.shape, Python.tuple(2, 3))
15+
16+
17+
def main():
18+
test_basic_numpy_example()

tests/python/requirements.txt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# This file was autogenerated by uv via the following command:
2+
# uv export -o requirements.txt
3+
numpy==2.2.5 \
4+
--hash=sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a \
5+
--hash=sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4 \
6+
--hash=sha256:0cd48122a6b7eab8f06404805b1bd5856200e3ed6f8a1b9a194f9d9054631beb \
7+
--hash=sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e \
8+
--hash=sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9 \
9+
--hash=sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d \
10+
--hash=sha256:2c1a1c6ccce4022383583a6ded7bbcda22fc635eb4eb1e0a053336425ed36dfa \
11+
--hash=sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376 \
12+
--hash=sha256:3a801fef99668f309b88640e28d261991bfad9617c27beda4a3aec4f217ea073 \
13+
--hash=sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372 \
14+
--hash=sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191 \
15+
--hash=sha256:47834cde750d3c9f4e52c6ca28a7361859fcaf52695c7dc3cc1a720b8922683e \
16+
--hash=sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f \
17+
--hash=sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73 \
18+
--hash=sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0 \
19+
--hash=sha256:5a0ac90e46fdb5649ab6369d1ab6104bfe5854ab19b645bf5cda0127a13034ae \
20+
--hash=sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19 \
21+
--hash=sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba \
22+
--hash=sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133 \
23+
--hash=sha256:9d75f338f5f79ee23548b03d801d28a505198297534f62416391857ea0479571 \
24+
--hash=sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7 \
25+
--hash=sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291 \
26+
--hash=sha256:abe38cd8381245a7f49967a6010e77dbf3680bd3627c0fe4362dd693b404c7f8 \
27+
--hash=sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066 \
28+
--hash=sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b \
29+
--hash=sha256:ced69262a8278547e63409b2653b372bf4baff0870c57efa76c5703fd6543282 \
30+
--hash=sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8 \
31+
--hash=sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471 \
32+
--hash=sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6 \
33+
--hash=sha256:ec31367fd6a255dc8de4772bd1658c3e926d8e860a0b6e922b615e532d320ddc \
34+
--hash=sha256:ee461a4eaab4f165b68780a6a1af95fb23a29932be7569b9fab666c407969051

0 commit comments

Comments
 (0)