rules_python icon indicating copy to clipboard operation
rules_python copied to clipboard

feat: render aliases for `extra_targets` of `whl_mods`.

Open lalten opened this issue 1 year ago • 1 comments

I'm trying to get rules_ros2 to Bzlmod. There is a dependency on numpy headers that are exposed via an additive_build_content cc_library. One of the last migration challenges is how to properly consume these headers in both Bzlmod and Workspace and without hardcoding a Python version. See https://github.com/mvukov/rules_ros2/pull/238#discussion_r1549195117 for the WIP PR.

I dug a little into how the aliasing works and come up with a proof of concept of how a list of extra_targets could be propagated from pip.whl_mods like

pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
pip.whl_mods(
    additive_build_content = """\
cc_library(
    name = "headers",
    hdrs = glob(["site-packages/numpy/core/include/numpy/**/*.h"]),
    includes = ["site-packages/numpy/core/include"],
    deps = ["@rules_python//python/cc:current_py_cc_headers"],
)
""",
    extra_targets = ["headers"],  # <--- this is new
    hub_name = "whl_mods_hub",
    whl_name = "numpy",
)
use_repo(pip, "whl_mods_hub")

This makes it possible to depend on "@rules_ros2_pip_deps//numpy:headers" instead of e.g. "@rules_ros2_pip_deps_310_numpy//:headers

Workspace doesn't have this problem, but the label looks slightly different. That can be worked around with an alias for now.

load("@rules_python//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")
alias(
    name = "rules_ros2_pip_deps_numpy_headers",
    actual = "@rules_ros2_pip_deps//numpy:headers" if BZLMOD_ENABLED else "@rules_ros2_pip_deps_numpy//:headers",
    visibility = ["//visibility:public"],
)

You can see this in action in https://github.com/mvukov/rules_ros2/pull/238/commits/e2b926c03075913754a8d8175e356a5637b0f5c7

Please let me know what you think of this. Maybe there is a more elegant way to achieve this?

lalten avatar May 15 '24 23:05 lalten

Thanks for this PR! At some point I had experimented with different ideas on this topic and this solution was one of them.

Other solutions that could achieve the goal in the repository context:

  • We could as well parse the additive_build_content and get all of the lines that start with name = " and that is how we could get the extra aliases that we need to get. That way the user does not need to define the target names in two places.
  • We could have regular patch file interface to hub repo BUILD.bazel files.

However, a better way could be to solve this in the build phase where we use one of the predefined targets and we can extract the headers, the easiest way that I could figure out was:

genrule(
    name = "numpy_headers",
    srcs = ["@pypi//numpy:whl"],
    outs = [
        # "numpy/core/include/numpy/__multiarray_api.c",
        # "numpy/core/include/numpy/__multiarray_api.h",
        # "numpy/core/include/numpy/__ufunc_api.c",
        # "numpy/core/include/numpy/__ufunc_api.h",
        "numpy/core/include/numpy/_dtype_api.h",
        "numpy/core/include/numpy/_neighborhood_iterator_imp.h",
        "numpy/core/include/numpy/_numpyconfig.h",
        "numpy/core/include/numpy/arrayobject.h",
        "numpy/core/include/numpy/arrayscalars.h",
        "numpy/core/include/numpy/experimental_dtype_api.h",
        "numpy/core/include/numpy/halffloat.h",
        "numpy/core/include/numpy/ndarrayobject.h",
        "numpy/core/include/numpy/ndarraytypes.h",
        "numpy/core/include/numpy/noprefix.h",
        "numpy/core/include/numpy/npy_1_7_deprecated_api.h",
        "numpy/core/include/numpy/npy_3kcompat.h",
        "numpy/core/include/numpy/npy_common.h",
        "numpy/core/include/numpy/npy_cpu.h",
        "numpy/core/include/numpy/npy_endian.h",
        "numpy/core/include/numpy/npy_interrupt.h",
        "numpy/core/include/numpy/npy_math.h",
        "numpy/core/include/numpy/npy_no_deprecated_api.h",
        "numpy/core/include/numpy/npy_os.h",
        "numpy/core/include/numpy/numpyconfig.h",
        "numpy/core/include/numpy/old_defines.h",
        "numpy/core/include/numpy/random/bitgen.h",
        "numpy/core/include/numpy/random/distributions.h",
        "numpy/core/include/numpy/random/libdivide.h",
        "numpy/core/include/numpy/ufuncobject.h",
        "numpy/core/include/numpy/utils.h",
    ],
    cmd = "unzip $< -d $(RULEDIR) -x $(OUTS)",
)

I wonder if there are better ways to extracting filegroups from the whls. A custom build rule that extract particular files from a whl could be the best solution because that is something that can live in @rules_python and not in the third party repos.

The API that I think we could have could be

load("@rules_python//python:pip.bzl", "whl_filegroup")

whl_filegroup(
    name = "numpy_includes",
    whl = "@pypi//numpy:whl",
    prefixes = [
        "numpy/core/include/numpy",
    ],
)

Because each whl has a RECORD file, we could use that RECORD file as the index of files that we need to expose in the outputs of the build rule.

aignas avatar May 15 '24 23:05 aignas