Repository: conan-io/conan
Pull request #18424 (2025-06-06 14:55:45)

[BazelDeps] Pure refactor
Changelog: omit
Docs: omit



Patch:
diff --git a/conan/internal/rest/rest_client_local_recipe_index.py b/conan/internal/rest/rest_client_local_recipe_index.py
index 33082ca13..ebc2e8c18 100644
--- a/conan/internal/rest/rest_client_local_recipe_index.py
+++ b/conan/internal/rest/rest_client_local_recipe_index.py
@@ -210,6 +210,8 @@ class _LocalRecipesIndexLayout:
             pattern = f"{pattern_ref.name}/{pattern_ref.version}"
         except:
             # pattern = pattern
+            pattern = f"{name_pattern}/*"
+            original_pattern = pattern
             pass
 
         loader = ConanFileLoader(None)
diff --git a/conan/tools/google/bazeldeps.py b/conan/tools/google/bazeldeps.py
index f2202b417..ad77246f3 100644
--- a/conan/tools/google/bazeldeps.py
+++ b/conan/tools/google/bazeldeps.py
@@ -1,121 +1,28 @@
 import os
 import re
 import textwrap
-from collections import namedtuple, defaultdict
 
 from jinja2 import Template, StrictUndefined
 
-from conan.errors import ConanException
 from conan.internal import check_duplicated_generator
 from conan.internal.model.dependencies import get_transitive_requires
 from conan.internal.model.pkg_type import PackageType
 from conan.internal.util.files import save
 
-_BazelTargetInfo = namedtuple("DepInfo", ['repository_name', 'name', 'ref_name', 'requires', 'cpp_info'])
 
-
-def _get_name_with_namespace(namespace, name):
-    """
-    Build a name with a namespace, e.g., openssl-crypto
-    """
-    return f"{namespace}-{name}"
-
-
-def _get_package_reference_name(dep):
-    """
-    Get the reference name for the given package
-    """
-    return dep.ref.name
-
-
-def _get_repository_name(dep, is_build_require=False):
-    pkg_name = dep.cpp_info.get_property("bazel_repository_name") or _get_package_reference_name(dep)
-    return f"build-{pkg_name}" if is_build_require else pkg_name
-
-
-def _get_target_name(dep):
-    pkg_name = dep.cpp_info.get_property("bazel_target_name") or _get_package_reference_name(dep)
-    return pkg_name
-
-
-def _get_component_name(dep, comp_ref_name):
-    pkg_name = _get_target_name(dep)
-    if comp_ref_name not in dep.cpp_info.components:
-        # foo::foo might be referencing the root cppinfo
-        if _get_package_reference_name(dep) == comp_ref_name:
-            return pkg_name
-        raise ConanException("Component '{name}::{cname}' not found in '{name}' "
-                             "package requirement".format(name=_get_package_reference_name(dep),
-                                                          cname=comp_ref_name))
-    comp_name = dep.cpp_info.components[comp_ref_name].get_property("bazel_target_name")
-    # If user did not set bazel_target_name, let's create a component name
-    # with a namespace, e.g., dep-comp1
-    return comp_name or _get_name_with_namespace(pkg_name, comp_ref_name)
-
-
-# FIXME: This function should be a common one to be used by PkgConfigDeps, CMakeDeps?, etc.
-def _get_requirements(conanfile, build_context_activated):
-    """
-    Simply save the activated requirements (host + build + test), and the deactivated ones
-    """
-    # All the requirements
-    host_req = conanfile.dependencies.host
-    build_req = conanfile.dependencies.direct_build  # tool_requires
-    test_req = conanfile.dependencies.test
-
-    for require, dep in list(host_req.items()) + list(build_req.items()) + list(test_req.items()):
-        # Require is not used at the moment, but its information could be used,
-        # and will be used in Conan 2.0
-        # Filter the build_requires not activated with self.build_context_activated
-        if require.build and dep.ref.name not in build_context_activated:
-            continue
-        yield require, dep
-
-
-def _get_headers(cpp_info, package_folder_path):
-    return ['"{}/**"'.format(_relativize_path(path, package_folder_path))
-            for path in cpp_info.includedirs]
-
-
-def _get_includes(cpp_info, package_folder_path):
-    return ['"{}"'.format(_relativize_path(path, package_folder_path))
-            for path in cpp_info.includedirs]
-
-
-def _get_defines(cpp_info):
-    return ['"{}"'.format(define.replace('"', '\\' * 3 + '"'))
-            for define in cpp_info.defines]
-
-
-def _get_linkopts(cpp_info, os_build):
-    link_opt = '/DEFAULTLIB:{}' if os_build == "Windows" else '-l{}'
-    system_libs = [link_opt.format(lib) for lib in cpp_info.system_libs]
-    shared_flags = cpp_info.sharedlinkflags + cpp_info.exelinkflags
-    return [f'"{flag}"' for flag in (system_libs + shared_flags)]
-
-
-def _get_copts(cpp_info):
-    # FIXME: long discussions between copts (-Iflag) vs includes in Bazel. Not sure yet
-    # includedirsflags = ['"-I{}"'.format(_relativize_path(d, package_folder_path))
-    #                     for d in cpp_info.includedirs]
-    cxxflags = [var.replace('"', '\\"') for var in cpp_info.cxxflags]
-    cflags = [var.replace('"', '\\"') for var in cpp_info.cflags]
-    return [f'"{flag}"' for flag in (cxxflags + cflags)]
-
-
-def _relativize_path(path, pattern):
+def _relativize_path(path, start_folder):
     """
-    Returns a relative path with regard to pattern given.
+    Returns a relative path with regard to the given folder.
 
-    :param path: absolute or relative path
-    :param pattern: either a piece of path or a pattern to match the leading part of the path
+    :param path: absolute or relative path.
+    :param start_folder: folder to start relative to.
     :return: Unix-like path relative if matches to the given pattern.
              Otherwise, it returns the original path.
     """
-    if not path or not pattern:
+    if not path or not start_folder:
         return path
     path_ = path.replace("\\", "/").replace("/./", "/")
-    pattern_ = pattern.replace("\\", "/").replace("/./", "/")
+    pattern_ = start_folder.replace("\\", "/").replace("/./", "/")
     match = re.match(pattern_, path_)
     if match:
         matching = match[0]
@@ -125,157 +32,26 @@ def _relativize_path(path, pattern):
     return path
 
 
-class _BazelDependenciesBZLGenerator:
-    """
-    Bazel 6.0 needs to know all the dependencies for its current project. So, the only way
-    to do that is to tell the WORKSPACE file how to load all the Conan ones. This is the goal
-    of the function created by this class, the ``load_conan_dependencies`` one.
-
-    More information:
-        * https://bazel.build/reference/be/workspace#new_local_repository
-
-    Bazel >= 7.1 needs to know all the dependencies as well, but provided via the MODULE.bazel file.
-    Therefor we provide a static repository rule to load the dependencies. This rule is used by a
-    module extension, passing the package path and the BUILD file path to the repository rule.
-    """
-
-    repository_filename = "dependencies.bzl"
-    modules_filename = "conan_deps_module_extension.bzl"
-    repository_rules_filename = "conan_deps_repo_rules.bzl"
-    repository_template = textwrap.dedent("""\
-        # This Bazel module should be loaded by your WORKSPACE file.
-        # Add these lines to your WORKSPACE one (assuming that you're using the "bazel_layout"):
-        # load("@//conan:dependencies.bzl", "load_conan_dependencies")
-        # load_conan_dependencies()
-
-        def load_conan_dependencies():
-        {% for repository_name, pkg_folder, pkg_build_file_path in dependencies %}
-            native.new_local_repository(
-                name="{{repository_name}}",
-                path="{{pkg_folder}}",
-                build_file="{{pkg_build_file_path}}",
-            )
-        {% endfor %}
-        """)
-    module_template = textwrap.dedent("""\
-        # This module provides a repo for each requires-dependency in your conanfile.
-        # It's generated by the BazelDeps, and should be used in your Module.bazel file.
-        load(":conan_deps_repo_rules.bzl", "conan_dependency_repo")
-
-        def _load_dependenies_impl(mctx):
-        {% for repository_name, pkg_folder, pkg_build_file_path in dependencies %}
-            conan_dependency_repo(
-                name = "{{repository_name}}",
-                package_path = "{{pkg_folder}}",
-                build_file_path = "{{pkg_build_file_path}}",
-            )
-        {% endfor %}
-
-            return mctx.extension_metadata(
-                # It will only warn you if any direct
-                # dependency is not imported by the 'use_repo' or even it is imported
-                # but not created. Notice that root_module_direct_dev_deps can not be None as we
-                # are giving 'all' value to root_module_direct_deps.
-                # Fix the 'use_repo' calls by running 'bazel mod tidy'
-                root_module_direct_deps = 'all',
-                root_module_direct_dev_deps = [],
-
-                # Prevent writing function content to lockfiles:
-                # - https://bazel.build/rules/lib/builtins/module_ctx#extension_metadata
-                # Important for remote build. Actually it's not reproducible, as local paths will
-                # be different on different machines. But we assume that conan works correctly here.
-                # IMPORTANT: Not compatible with bazel < 7.1
-                reproducible = True,
-            )
-
-        conan_extension = module_extension(
-            implementation = _load_dependenies_impl,
-            os_dependent = True,
-            arch_dependent = True,
-        )
-        """)
-    repository_rules_content = textwrap.dedent("""\
-        # This bazel repository rule is used to load Conan dependencies into the Bazel workspace.
-        # It's used by a generated module file that provides information about the conan packages.
-        # Each conan package is loaded into a bazel repository rule, with having the name of the
-        # package. The whole method is based on symlinks to not copy the whole package into the
-        # Bazel workspace, which is expensive.
-        def _conan_dependency_repo(rctx):
-            package_path = rctx.workspace_root.get_child(rctx.attr.package_path)
-
-            child_packages = package_path.readdir()
-            for child in child_packages:
-                rctx.symlink(child, child.basename)
-
-            rctx.symlink(rctx.attr.build_file_path, "BUILD.bazel")
-
-        conan_dependency_repo = repository_rule(
-            implementation = _conan_dependency_repo,
-            attrs = {
-                "package_path": attr.string(
-                    mandatory = True,
-                    doc = "The path to the Conan package in conan cache.",
-                ),
-                "build_file_path": attr.string(
-                    mandatory = True,
-                    doc = "The path to the BUILD file.",
-                ),
-            },
-        )
-        """)
-
-    def __init__(self, conanfile, dependencies):
-        self._conanfile = conanfile
-        self._dependencies = dependencies
-
-    def _generate_6x_compatible(self):
-        repository_template = Template(self.repository_template, trim_blocks=True,
-                                       lstrip_blocks=True,
-                                       undefined=StrictUndefined)
-        content = repository_template.render(dependencies=self._dependencies)
-        # dependencies.bzl file (Bazel 6.x compatible)
-        save(self.repository_filename, content)
-
-    def generate(self):
-        # Keeping available Bazel 6.x, but it'll likely be dropped soon
-        self._generate_6x_compatible()
-        # Bazel 7.x files
-        module_template = Template(self.module_template, trim_blocks=True, lstrip_blocks=True,
-                                   undefined=StrictUndefined)
-        content = module_template.render(dependencies=self._dependencies)
-        save(self.modules_filename, content)
-        save(self.repository_rules_filename, self.repository_rules_content)
-        save("BUILD.bazel", "# This is an empty BUILD file.")
-
-
-class _LibInfo:
-    def __init__(self, lib_name, cpp_info_, package_folder_path):
-        self.name = lib_name
-        self.is_shared = cpp_info_.type == PackageType.SHARED
-        self.lib_path = _relativize_path(cpp_info_.location, package_folder_path)
-        self.import_lib_path = _relativize_path(cpp_info_.link_location, package_folder_path)
-
-
-class _BazelBUILDGenerator:
+class _BazelDepBuildGenerator:
     """
     This class creates the BUILD.bazel for each dependency where it's declared all the
     necessary information to load the libraries
     """
     # If both files exist, BUILD.bazel takes precedence over BUILD
     # https://bazel.build/concepts/build-files
-    filename = "BUILD.bazel"
-    template = textwrap.dedent("""\
+    dep_build_filename = "BUILD.bazel"
+    dep_build_template = textwrap.dedent("""\
     {% macro cc_import_macro(libs) %}
     {% for lib_info in libs %}
     cc_import(
-        name = "{{ lib_info.name }}_precompiled",
-        {% if lib_info.is_shared %}
-        shared_library = "{{ lib_info.lib_path }}",
+        name = "{{ lib_info['name'] }}_precompiled",
+        {% if lib_info['is_shared'] %}
+        shared_library = "{{ lib_info['lib_path'] }}",
         {% else %}
-        static_library = "{{ lib_info.lib_path }}",
+        static_library = "{{ lib_info['lib_path'] }}",
         {% endif %}
-        {% if lib_info.import_lib_path %}
-        interface_library = "{{ lib_info.import_lib_path }}",
+        {% if lib_info['import_lib_path'] %}
+        interface_library = "{{ lib_info['import_lib_path'] }}",
         {% endif %}
     )
     {% endfor %}
@@ -319,13 +95,13 @@ class _BazelBUILDGenerator:
         ],
         {% endif %}
         visibility = ["//visibility:public"],
-        {% if obj["libs"] or obj["dependencies"] or obj["component_names"] %}
+        {% if obj["libs"] or obj["dependencies"] or obj.get("component_names", []) %}
         deps = [
             # do not sort
             {% for lib in obj["libs"] %}
             ":{{ lib.name }}_precompiled",
             {% endfor %}
-            {% for name in obj["component_names"] %}
+            {% for name in obj.get("component_names", []) %}
             ":{{ name }}",
             {% endfor %}
             {% for dep in obj["dependencies"] %}
@@ -366,30 +142,30 @@ class _BazelBUILDGenerator:
     {{ filegroup_bindirs_macro(root) }}
     """)
 
-    def __init__(self, conanfile, dep, root_package_info, components_info):
+    def __init__(self, conanfile, dep, require):
         self._conanfile = conanfile
         self._dep = dep
-        self._root_package_info = root_package_info
-        self._components_info = components_info
+        self._is_build_require = require.build
+        self._transitive_reqs = get_transitive_requires(self._conanfile, dep)
 
     @property
-    def build_file_pah(self):
+    def _build_file_path(self):
         """
         Returns the absolute path to the BUILD file created by Conan
         """
-        folder = os.path.join(self._root_package_info.repository_name, self.filename)
+        folder = os.path.join(self._get_repository_name(self._dep), self.dep_build_filename)
         return folder.replace("\\", "/")
 
     @property
-    def absolute_build_file_pah(self):
+    def _absolute_build_file_path(self):
         """
         Returns the absolute path to the BUILD file created by Conan
         """
-        folder = os.path.join(self._conanfile.generators_folder, self.build_file_pah)
+        folder = os.path.join(self._conanfile.generators_folder, self._build_file_path)
         return folder.replace("\\", "/")
 
     @property
-    def package_folder(self):
+    def _package_folder(self):
         """
         Returns the package folder path
         """
@@ -398,105 +174,55 @@ class _BazelBUILDGenerator:
             else self._dep.package_folder
         return root_folder.replace("\\", "/")
 
-    @property
-    def repository_name(self):
-        """
-        Wrapper to get the final name used for the root dependency cc_library declaration
-        """
-        return self._root_package_info.repository_name
-
-    def get_full_libs_info(self):
-        full_libs_info = defaultdict(list)
-        cpp_info = self._dep.cpp_info
-        deduced_cpp_info = cpp_info.deduce_full_cpp_info(self._dep)
-        package_folder_path = self.package_folder
-        # Root
-        if len(cpp_info.libs) > 1:
-            for lib_name in cpp_info.libs:
-                virtual_component = deduced_cpp_info.components.pop(f"_{lib_name}")  # removing it!
-                full_libs_info["root"].append(
-                    _LibInfo(lib_name, virtual_component, package_folder_path)
-                )
-        elif cpp_info.libs:
-            full_libs_info["root"].append(
-                _LibInfo(cpp_info.libs[0], deduced_cpp_info, package_folder_path)
-            )
-        # components
-        for cmp_name, cmp_cpp_info in deduced_cpp_info.components.items():
-            if cmp_name == "_common":  # FIXME: placeholder?
-                continue
-            if len(cmp_cpp_info.libs) > 1:
-                raise ConanException(f"BazelDeps only allows 1 lib per component:\n"
-                                     f"{self._dep}: {cmp_cpp_info.libs}")
-            elif cmp_cpp_info.libs:
-                # Bazel needs to relativize each path
-                full_libs_info[_get_component_name(self._dep, cmp_name)].append(
-                    _LibInfo(cmp_cpp_info.libs[0], cmp_cpp_info, package_folder_path)
-                )
-        return full_libs_info
-
-    def _get_context(self):
-        def fill_info(info, libs_info):
-            ret = {
-                "name": info.name,  # package name and components name
-                "libs": {},
-                "headers": "",
-                "includes": "",
-                "defines": "",
-                "linkopts": "",
-                "copts": "",
-                "dependencies": info.requires,
-                "component_names": []  # filled only by the root
-            }
-            if info.cpp_info is not None:
-                cpp_info = info.cpp_info
-                headers = _get_headers(cpp_info, package_folder_path)
-                includes = _get_includes(cpp_info, package_folder_path)
-                copts = _get_copts(cpp_info)
-                defines = _get_defines(cpp_info)
-                os_build = self._dep.settings_build.get_safe("os")
-                linkopts = _get_linkopts(cpp_info, os_build)
-                bindirs = [_relativize_path(bindir, package_folder_path)
-                           for bindir in cpp_info.bindirs]
-                ret.update({
-                    "libs": libs_info,
-                    "bindirs": bindirs,
-                    "headers": headers,
-                    "includes": includes,
-                    "defines": defines,
-                    "linkopts": linkopts,
-                    "copts": copts
-                })
-            return ret
-
-        package_folder_path = self.package_folder
-        context = dict()
-        full_libs_info = self.get_full_libs_info()
-        context["root"] = fill_info(self._root_package_info, full_libs_info.get("root", []))
-        context["components"] = []
-        for component in self._components_info:
-            component_context = fill_info(component, full_libs_info.get(component.name, []))
-            context["components"].append(component_context)
-            context["root"]["component_names"].append(component_context["name"])
-
-        return context
-
-    def generate(self):
-        context = self._get_context()
-        template = Template(self.template, trim_blocks=True, lstrip_blocks=True,
-                            undefined=StrictUndefined)
-        content = template.render(context)
-        save(self.build_file_pah, content)
-
-
-class _InfoGenerator:
-
-    def __init__(self, conanfile, dep, require):
-        self._conanfile = conanfile
-        self._dep = dep
-        self._req = require
-        self._is_build_require = require.build
-        self._transitive_reqs = get_transitive_requires(self._conanfile, dep)
+    def _get_repository_name(self, dep):
+        pkg_name = dep.cpp_info.get_property("bazel_repository_name") or dep.ref.name
+        return f"build-{pkg_name}" if self._is_build_require else pkg_name
+
+    def _get_target_name(self, dep):
+        pkg_name = dep.cpp_info.get_property("bazel_target_name") or dep.ref.name
+        return pkg_name
+
+    def _get_component_name(self, dep, comp_ref_name):
+        pkg_name = self._get_target_name(dep)
+        if comp_ref_name not in dep.cpp_info.components:
+            # foo::foo might be referencing the root cppinfo
+            if dep.ref.name == comp_ref_name:
+                return pkg_name
+            return f"{pkg_name}-{comp_ref_name}"
+        comp_name = dep.cpp_info.components[comp_ref_name].get_property("bazel_target_name")
+        # If user did not set bazel_target_name, let's create a component name
+        # with a namespace, e.g., dep-comp1
+        return comp_name or f"{pkg_name}-{comp_ref_name}"
+
+    def _get_headers(self, cpp_info):
+        return ['"{}/**"'.format(_relativize_path(path, self._package_folder))
+                for path in cpp_info.includedirs]
+
+    def _get_bindirs(self, cpp_info):
+        return [_relativize_path(bindir, self._package_folder) for bindir in cpp_info.bindirs]
+
+    def _get_includes(self, cpp_info):
+        return ['"{}"'.format(_relativize_path(path, self._package_folder))
+                for path in cpp_info.includedirs]
+
+    def _get_defines(self, cpp_info):
+        return ['"{}"'.format(define.replace('"', '\\' * 3 + '"'))
+                for define in cpp_info.defines]
+
+    def _get_linkopts(self, cpp_info):
+        os_build = self._dep.settings_build.get_safe("os")
+        link_opt = '/DEFAULTLIB:{}' if os_build == "Windows" else '-l{}'
+        system_libs = [link_opt.format(lib) for lib in cpp_info.system_libs]
+        shared_flags = cpp_info.sharedlinkflags + cpp_info.exelinkflags
+        return [f'"{flag}"' for flag in (system_libs + shared_flags)]
+
+    def _get_copts(self, cpp_info):
+        # FIXME: long discussions between copts (-Iflag) vs includes in Bazel. Not sure yet
+        # includedirsflags = ['"-I{}"'.format(_relativize_path(d, package_folder_path))
+        #                     for d in cpp_info.includedirs]
+        cxxflags = [var.replace('"', '\\"') for var in cpp_info.cxxflags]
+        cflags = [var.replace('"', '\\"') for var in cpp_info.cflags]
+        return [f'"{flag}"' for flag in (cxxflags + cflags)]
 
     def _get_component_requirement_names(self, cpp_info):
         """
@@ -518,7 +244,7 @@ class _InfoGenerator:
                 self.cpp_info.components["cmp"].requires = ["other::cmp1"]
         ```
         """
-        dep_ref_name = _get_package_reference_name(self._dep)
+        dep_ref_name = self._dep.ref.name
         ret = []
         for req in cpp_info.requires:
             pkg_ref_name, comp_ref_name = req.split("::") if "::" in req else (dep_ref_name, req)
@@ -528,47 +254,71 @@ class _InfoGenerator:
                 try:
                     req_conanfile = self._transitive_reqs[pkg_ref_name]
                     # Requirements declared in another dependency BUILD file
-                    prefix = f"@{_get_repository_name(req_conanfile, is_build_require=self._is_build_require)}//:"
+                    prefix = f"@{self._get_repository_name(req_conanfile)}//:"
                 except KeyError:
                     continue  # If the dependency is not in the transitive, might be skipped
             else:  # For instance, dep == "hello/1.0" and req == "hello::cmp1" -> hello == hello
                 req_conanfile = self._dep
-            comp_name = _get_component_name(req_conanfile, comp_ref_name)
+            comp_name = self._get_component_name(req_conanfile, comp_ref_name)
             dep_name = f"{prefix}{comp_name}"
             if dep_name not in ret:
                 ret.append(dep_name)
         return ret
 
-    @property
-    def components_info(self):
-        """
-        Get the whole package and its components information like their own requires, names and even
-        the cpp_info for each component.
+    def _get_lib_info(self, cpp_info, deduced_cpp_info, component_name=None):
 
-        :return: `list` of `_BazelTargetInfo` objects with all the components information
-        """
-        if not self._dep.cpp_info.has_components:
-            return []
-        components_info = []
-        # Loop through all the package's components
-        for comp_ref_name, cpp_info in self._dep.cpp_info.get_sorted_components().items():
-            # At first, let's check if we have defined some components requires, e.g., "dep::cmp1"
-            comp_requires_names = self._get_component_requirement_names(cpp_info)
-            comp_name = _get_component_name(self._dep, comp_ref_name)
-            # Save each component information
-            components_info.append(_BazelTargetInfo(None, comp_name, comp_ref_name,
-                                                    comp_requires_names, cpp_info))
-        return components_info
+        def _lib_info(lib_name, virtual_cpp_info):
+            return {
+                "name": lib_name,
+                "is_shared": virtual_cpp_info.type == PackageType.SHARED,
+                "lib_path": _relativize_path(virtual_cpp_info.location, self._package_folder),
+                "import_lib_path": _relativize_path(virtual_cpp_info.link_location, self._package_folder)
+            }
 
-    @property
-    def root_package_info(self):
+        libs = cpp_info.libs
+        libs_info = []
+        if libs:
+            if len(libs) > 1:
+                for lib_name in libs:
+                    name = f'_{component_name}_{lib_name}' if component_name else f'_{lib_name}'
+                    virtual_cpp_info = deduced_cpp_info.components[name]
+                    libs_info.append(_lib_info(lib_name, virtual_cpp_info))
+            else:
+                lib_name = libs[0]
+                virtual_cpp_info = deduced_cpp_info.components[component_name] if component_name else deduced_cpp_info
+                libs_info.append(_lib_info(lib_name, virtual_cpp_info))
+        return libs_info
+
+    def _get_build_file_context(self):
         """
         Get the whole package information
 
         :return: `_BazelTargetInfo` object with the package information
         """
-        repository_name = _get_repository_name(self._dep, is_build_require=self._is_build_require)
-        pkg_name = _get_target_name(self._dep)
+        build_content = {"components": [], "root": {}}
+        component_names = []
+        deduced_cpp_info = self._dep.cpp_info.deduce_full_cpp_info(self._dep)
+
+        if self._dep.cpp_info.has_components:
+            # Loop through all the package's components
+            for comp_ref_name, cmp_cpp_info in self._dep.cpp_info.get_sorted_components().items():
+                # At first, let's check if we have defined some components requires, e.g., "dep::cmp1"
+                comp_requires_names = self._get_component_requirement_names(cmp_cpp_info)
+                comp_name = self._get_component_name(self._dep, comp_ref_name)
+                component_names.append(comp_name)
+                build_content["components"].append({
+                    "name": comp_name,
+                    "libs": self._get_lib_info(cmp_cpp_info, deduced_cpp_info, component_name=comp_ref_name),
+                    "bindirs": self._get_bindirs(cmp_cpp_info),
+                    "headers": self._get_headers(cmp_cpp_info),
+                    "includes": self._get_includes(cmp_cpp_info),
+                    "defines": self._get_defines(cmp_cpp_info),
+                    "linkopts": self._get_linkopts(cmp_cpp_info),
+                    "copts": self._get_copts(cmp_cpp_info),
+                    "dependencies": comp_requires_names,
+                })
+
+        pkg_name = self._get_target_name(self._dep)
         # At first, let's check if we have defined some global requires, e.g., "other::cmp1"
         requires = self._get_component_requirement_names(self._dep.cpp_info)
         # If we have found some component requires it would be enough
@@ -576,12 +326,159 @@ class _InfoGenerator:
             # If no requires were found, let's try to get all the direct dependencies,
             # e.g., requires = "other_pkg/1.0"
             requires = [
-                f"@{_get_repository_name(req, is_build_require=self._is_build_require)}//:{_get_target_name(req)}"
+                f"@{self._get_repository_name(req)}//:{self._get_target_name(req)}"
                 for req in self._transitive_reqs.values()
             ]
         cpp_info = self._dep.cpp_info
-        return _BazelTargetInfo(repository_name, pkg_name, _get_package_reference_name(self._dep),
-                                requires, cpp_info)
+        build_content["root"] = {
+            "name": pkg_name,
+            "libs": self._get_lib_info(cpp_info, deduced_cpp_info),
+            "bindirs": self._get_bindirs(cpp_info),
+            "headers": self._get_headers(cpp_info),
+            "includes": self._get_includes(cpp_info),
+            "defines": self._get_defines(cpp_info),
+            "linkopts": self._get_linkopts(cpp_info),
+            "copts": self._get_copts(cpp_info),
+            "dependencies": requires,
+            "component_names": component_names
+        }
+        return build_content
+
+    @property
+    def dep_context(self):
+        """
+        Return the dependency context to fill later the conan_deps_module_extension.bzl and so on.
+        """
+        return {
+            'repository_name': self._get_repository_name(self._dep),
+            'package_folder': self._package_folder,
+            'package_build_file_path': self._absolute_build_file_path,
+        }
+
+    def items(self):
+        template = Template(self.dep_build_template, trim_blocks=True, lstrip_blocks=True,
+                            undefined=StrictUndefined)
+        content = template.render(self._get_build_file_context())
+        return {self._build_file_path: content}.items()
+
+
+class _BazelPathsGenerator:
+    """
+    Bazel 6.0 needs to know all the dependencies for its current project. So, the only way
+    to do that is to tell the WORKSPACE file how to load all the Conan ones. This is the goal
+    of the function created by this class, the ``load_conan_dependencies`` one.
+
+    More information:
+        * https://bazel.build/reference/be/workspace#new_local_repository
+
+    Bazel >= 7.1 needs to know all the dependencies as well, but provided via the MODULE.bazel file.
+    Therefor we provide a static repository rule to load the dependencies. This rule is used by a
+    module extension, passing the package path and the BUILD file path to the repository rule.
+    """
+    repository_filename = "dependencies.bzl"
+    modules_filename = "conan_deps_module_extension.bzl"
+    repository_rules_filename = "conan_deps_repo_rules.bzl"
+    repository_template = textwrap.dedent("""\
+        # This Bazel module should be loaded by your WORKSPACE file.
+        # Add these lines to your WORKSPACE one (assuming that you're using the "bazel_layout"):
+        # load("@//conan:dependencies.bzl", "load_conan_dependencies")
+        # load_conan_dependencies()
+
+        def load_conan_dependencies():
+        {% for dep_info in dependencies %}
+            native.new_local_repository(
+                name="{{dep_info['repository_name']}}",
+                path="{{dep_info['package_folder']}}",
+                build_file="{{dep_info['package_build_file_path']}}",
+            )
+        {% endfor %}
+        """)
+    module_template = textwrap.dedent("""\
+        # This module provides a repo for each requires-dependency in your conanfile.
+        # It's generated by the BazelDeps, and should be used in your Module.bazel file.
+        load(":conan_deps_repo_rules.bzl", "conan_dependency_repo")
+
+        def _load_dependencies_impl(mctx):
+        {% for dep_info in dependencies %}
+            conan_dependency_repo(
+                name = "{{dep_info['repository_name']}}",
+                package_path = "{{dep_info['package_folder']}}",
+                build_file_path = "{{dep_info['package_build_file_path']}}",
+            )
+        {% endfor %}
+
+            return mctx.extension_metadata(
+                # It will only warn you if any direct
+                # dependency is not imported by the 'use_repo' or even it is imported
+                # but not created. Notice that root_module_direct_dev_deps can not be None as we
+                # are giving 'all' value to root_module_direct_deps.
+                # Fix the 'use_repo' calls by running 'bazel mod tidy'
+                root_module_direct_deps = 'all',
+                root_module_direct_dev_deps = [],
+
+                # Prevent writing function content to lockfiles:
+                # - https://bazel.build/rules/lib/builtins/module_ctx#extension_metadata
+                # Important for remote build. Actually it's not reproducible, as local paths will
+                # be different on different machines. But we assume that conan works correctly here.
+                # IMPORTANT: Not compatible with bazel < 7.1
+                reproducible = True,
+            )
+
+        conan_extension = module_extension(
+            implementation = _load_dependencies_impl,
+            os_dependent = True,
+            arch_dependent = True,
+        )
+        """)
+    repository_rules_content = textwrap.dedent("""\
+        # This bazel repository rule is used to load Conan dependencies into the Bazel workspace.
+        # It's used by a generated module file that provides information about the conan packages.
+        # Each conan package is loaded into a bazel repository rule, with having the name of the
+        # package. The whole method is based on symlinks to not copy the whole package into the
+        # Bazel workspace, which is expensive.
+        def _conan_dependency_repo(rctx):
+            package_path = rctx.workspace_root.get_child(rctx.attr.package_path)
+
+            child_packages = package_path.readdir()
+            for child in child_packages:
+                rctx.symlink(child, child.basename)
+
+            rctx.symlink(rctx.attr.build_file_path, "BUILD.bazel")
+
+        conan_dependency_repo = repository_rule(
+            implementation = _conan_dependency_repo,
+            attrs = {
+                "package_path": attr.string(
+                    mandatory = True,
+                    doc = "The path to the Conan package in conan cache.",
+                ),
+                "build_file_path": attr.string(
+                    mandatory = True,
+                    doc = "The path to the BUILD file.",
+                ),
+            },
+        )
+        """)
+
+    @classmethod
+    def items(cls, dependencies_context):
+        if not dependencies_context:
+            return {}
+        # Bazel 6.x, but it'll likely be dropped soon
+        repository_template = Template(cls.repository_template, trim_blocks=True,
+                                       lstrip_blocks=True,
+                                       undefined=StrictUndefined)
+        content_6x = repository_template.render(dependencies=dependencies_context)
+        # Bazel 7.x files
+        module_template = Template(cls.module_template, trim_blocks=True, lstrip_blocks=True,
+                                   undefined=StrictUndefined)
+        content = module_template.render(dependencies=dependencies_context)
+        return {
+            cls.repository_filename: content_6x,  # bazel 6.x compatible
+            cls.modules_filename: content,
+            cls.repository_rules_filename: cls.repository_rules_content,
+            "BUILD.bazel": "# This is an empty BUILD file."  # Bazel needs this file in each subfolder
+        }.items()
 
 
 class BazelDeps:
@@ -594,6 +491,24 @@ class BazelDeps:
         #: Activates the build context for the specified Conan package names.
         self.build_context_activated = []
 
+    def _get_requirements(self, build_context_activated):
+        """
+        Simply save the activated requirements (host + build + test), and the deactivated ones
+        """
+        # All the requirements
+        host_req = self._conanfile.dependencies.host
+        build_req = self._conanfile.dependencies.direct_build  # tool_requires
+        test_req = self._conanfile.dependencies.test
+
+        for require, dep in list(host_req.items()) + list(build_req.items()) + list(
+            test_req.items()):
+            # Require is not used at the moment, but its information could be used,
+            # and will be used in Conan 2.0
+            # Filter the build_requires not activated with self.build_context_activated
+            if require.build and dep.ref.name not in build_context_activated:
+                continue
+            yield require, dep
+
     def generate(self):
         """
         Generates all the targets <DEP>/BUILD.bazel files, a dependencies.bzl (for bazel<7), a
@@ -620,25 +535,13 @@ class BazelDeps:
             use_repo(load_conan_dependencies, "dep-1", "dep-2", ...)
         """
         check_duplicated_generator(self, self._conanfile)
-        requirements = _get_requirements(self._conanfile, self.build_context_activated)
-        deps_info = []
-        for require, dep in requirements:
+        dependencies_context = []
+        for require, dep in self._get_requirements(self.build_context_activated):
             # Bazel info generator
-            info_generator = _InfoGenerator(self._conanfile, dep, require)
-            root_package_info = info_generator.root_package_info
-            components_info = info_generator.components_info
-            # Generating single BUILD files per dependency
-            bazel_generator = _BazelBUILDGenerator(self._conanfile, dep,
-                                                   root_package_info, components_info)
-            bazel_generator.generate()
-            # Saving pieces of information from each BUILD file
-            deps_info.append((
-                bazel_generator.repository_name,  # Bazel repository name == @repository_name
-                bazel_generator.package_folder,  # path to the Conan dependency folder
-                bazel_generator.absolute_build_file_pah  # path to the BUILD.bazel file created
-            ))
-        if deps_info:
-            # dependencies.bzl has all the information about where to look for the dependencies
-            bazel_dependencies_module_generator = _BazelDependenciesBZLGenerator(self._conanfile,
-                                                                                 deps_info)
-            bazel_dependencies_module_generator.generate()
+            dep_build_generator = _BazelDepBuildGenerator(self._conanfile, dep, require)
+            dependencies_context.append(dep_build_generator.dep_context)
+            for name, content in dep_build_generator.items():
+                save(name, content)
+
+        for name, content in _BazelPathsGenerator.items(dependencies_context):
+            save(name, content)


Test patch:
diff --git a/test/integration/command/list/list_test.py b/test/integration/command/list/list_test.py
index fddcacf42..5d04ed535 100644
--- a/test/integration/command/list/list_test.py
+++ b/test/integration/command/list/list_test.py
@@ -10,10 +10,14 @@ import pytest
 
 from conan.internal.errors import ConanConnectionError
 from conan.errors import ConanException
+from conan.test.assets.cmake import gen_cmakelists
 from conan.test.assets.genconanfile import GenConanfile
-from conan.test.utils.tools import TestClient, TestServer, NO_SETTINGS_PACKAGE_ID
+from conan.test.assets.sources import gen_function_h, gen_function_cpp
+from conan.test.utils.file_server import TestFileServer
+from conan.test.utils.test_files import temp_folder
+from conan.test.utils.tools import TestClient, TestServer, NO_SETTINGS_PACKAGE_ID, zipdir
 from conan.test.utils.env import environment_update
-from conan.internal.util.files import save
+from conan.internal.util.files import save, save_files, sha256sum
 
 
 class TestParamErrors:
@@ -947,3 +951,31 @@ def test_overlapping_versions():
     tc.run("list * -c -f=json", redirect_stdout="list.json")
     results = json.loads(tc.load("list.json"))
     assert len(results["Local Cache"]) == 2
+
+def test_list_local_recipe_index():
+    c = TestClient(light=True)
+    c.run(f"new local_recipes_index -d name=pkg -d version=0.1 -d url='https://conan-fake-url.com' ")
+    c.run(f"remote add local '{c.current_folder}'")
+
+    c.run("list '*' -r=local")
+    assert "Found 1 pkg/version recipes matching * in local" in c.out
+    c.run("list '*/*' -r=local")
+    assert "Found 1 pkg/version recipes matching */* in local" in c.out
+    c.run("list '*/0.1' -r=local")
+    assert "Found 1 pkg/version recipes matching */0.1 in local" in c.out
+    c.run("list 'pkg/*' -r=local")
+    assert "Found 1 pkg/version recipes matching pkg/* in local" in c.out
+    c.run("list 'pkg/0.*' -r=local")
+    assert "Found 1 pkg/version recipes matching pkg/0.* in local" in c.out
+    c.run("list 'pkg' -r=local")
+    assert "Found 1 pkg/version recipes matching pkg in local" in c.out
+    c.run("list 'pkg/0.1' -r=local")
+    assert "Found 1 pkg/version recipes matching pkg/0.1 in local" in c.out
+    c.run("list 'foo' -r=local")
+    assert "ERROR: Recipe 'foo' not found" in c.out
+    c.run("list '/#@&%%&@#/' -r=local")
+    assert "ERROR: Recipe '/' not found" in c.out
+    c.run("list 'pkg/0.1@a' -r=local")
+    assert "ERROR: Recipe 'pkg/0.1@a' not found" in c.out
+    c.run("list 'pkg%0.1#a@b/c' -r=local")
+    assert "ERROR: Recipe 'pkg%0.1' not found" in c.out
diff --git a/test/integration/toolchains/google/test_bazeldeps.py b/test/integration/toolchains/google/test_bazeldeps.py
index 843174fa8..d243b34cd 100644
--- a/test/integration/toolchains/google/test_bazeldeps.py
+++ b/test/integration/toolchains/google/test_bazeldeps.py
@@ -644,7 +644,7 @@ def test_with_editable_layout():
         # Bazel 7.x
         content = client.load("conan_deps_module_extension.bzl")
         assert textwrap.dedent(f"""\
-        def _load_dependenies_impl(mctx):
+        def _load_dependencies_impl(mctx):
             conan_dependency_repo(
                 name = "dep",
                 package_path = "{recipes_folder}/dep",