Repository: conan-io/conan
Pull request #16025 (2024-04-04 22:16:47)

Test for BazelDeps in the build context
Changelog: Bugfix: BazelDeps now uses the `requirement.build` property instead of `dependency.context` one.
Docs: Omit
Closes: https://github.com/conan-io/conan/issues/15764

Based on the tests that were done in the issue: #15763





Patch:
diff --git a/conan/cli/cli.py b/conan/cli/cli.py
index 7c9548212..a9a420c58 100644
--- a/conan/cli/cli.py
+++ b/conan/cli/cli.py
@@ -236,6 +236,14 @@ class Cli:
         return ERROR_UNEXPECTED
 
 
+def _warn_python_version():
+    version = sys.version_info
+    if version.minor == 6:
+        ConanOutput().warning("Python 3.6 is end-of-life since 2021. "
+                              "Conan future versions will drop support for it, "
+                              "please upgrade Python", warn_tag="deprecated")
+
+
 def main(args):
     """ main entry point of the conan application, using a Command to
     parse parameters
@@ -281,6 +289,7 @@ def main(args):
     error = SUCCESS
     try:
         cli.run(args)
+        _warn_python_version()
     except BaseException as e:
         error = cli.exception_exit_error(e)
     sys.exit(error)
diff --git a/conan/cli/commands/create.py b/conan/cli/commands/create.py
index 36b967882..d5b2e186e 100644
--- a/conan/cli/commands/create.py
+++ b/conan/cli/commands/create.py
@@ -40,7 +40,6 @@ def create(conan_api, parser, *args):
 
     cwd = os.getcwd()
     path = conan_api.local.get_conanfile_path(args.path, cwd, py=True)
-    test_conanfile_path = _get_test_conanfile_path(args.test_folder, path)
     overrides = eval(args.lockfile_overrides) if args.lockfile_overrides else None
     lockfile = conan_api.lockfile.get_lockfile(lockfile=args.lockfile,
                                                conanfile_path=path,
@@ -98,6 +97,9 @@ def create(conan_api, parser, *args):
         lockfile = conan_api.lockfile.update_lockfile(lockfile, deps_graph, args.lockfile_packages,
                                                       clean=args.lockfile_clean)
 
+    test_package_folder = getattr(conanfile, "test_package_folder", None) \
+        if args.test_folder is None else args.test_folder
+    test_conanfile_path = _get_test_conanfile_path(test_package_folder, path)
     # If the user provide --test-missing and the binary was not built from source, skip test_package
     if args.test_missing and deps_graph.root.dependencies\
             and deps_graph.root.dependencies[0].dst.binary != BINARY_BUILD:
diff --git a/conan/tools/google/bazeldeps.py b/conan/tools/google/bazeldeps.py
index 4dffc1c4d..b2d64f985 100644
--- a/conan/tools/google/bazeldeps.py
+++ b/conan/tools/google/bazeldeps.py
@@ -28,9 +28,9 @@ def _get_package_reference_name(dep):
     return dep.ref.name
 
 
-def _get_repository_name(dep):
+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 dep.context == "build" else pkg_name
+    return f"build-{pkg_name}" if is_build_require else pkg_name
 
 
 def _get_target_name(dep):
@@ -456,9 +456,11 @@ class _BazelBUILDGenerator:
 
 class _InfoGenerator:
 
-    def __init__(self, conanfile, dep):
+    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_cpp_info_requires_names(self, cpp_info):
@@ -491,7 +493,7 @@ 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)}//:"
+                    prefix = f"@{_get_repository_name(req_conanfile, is_build_require=self._is_build_require)}//:"
                 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
@@ -527,7 +529,7 @@ class _InfoGenerator:
 
         :return: `_BazelTargetInfo` object with the package information
         """
-        repository_name = _get_repository_name(self._dep)
+        repository_name = _get_repository_name(self._dep, is_build_require=self._is_build_require)
         pkg_name = _get_target_name(self._dep)
         # At first, let's check if we have defined some global requires, e.g., "other::cmp1"
         requires = self._get_cpp_info_requires_names(self._dep.cpp_info)
@@ -535,8 +537,10 @@ class _InfoGenerator:
         if not requires:
             # 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)}//:{_get_target_name(req)}"
-                        for req in self._transitive_reqs.values()]
+            requires = [
+                f"@{_get_repository_name(req, is_build_require=self._is_build_require)}//:{_get_target_name(req)}"
+                for req in self._transitive_reqs.values()
+            ]
         cpp_info = self._dep.cpp_info
         return _BazelTargetInfo(repository_name, pkg_name, requires, cpp_info)
 
@@ -567,7 +571,7 @@ class BazelDeps:
         deps_info = []
         for require, dep in requirements:
             # Bazel info generator
-            info_generator = _InfoGenerator(self._conanfile, dep)
+            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


Test patch:
diff --git a/conans/test/integration/command/remote_test.py b/conans/test/integration/command/remote_test.py
index 12cbdda74..b687fb592 100644
--- a/conans/test/integration/command/remote_test.py
+++ b/conans/test/integration/command/remote_test.py
@@ -247,7 +247,7 @@ class RemoteModificationTest(unittest.TestCase):
         for remote in data["remotes"]:
             self.assertEqual(remote["disabled"], True)
 
-        data = json.loads(client.out)
+        data = json.loads(client.stdout)
         for remote in data:
             assert remote["enabled"] is False
 
diff --git a/conans/test/integration/command/test_profile.py b/conans/test/integration/command/test_profile.py
index d066f45f7..db8e6d7b7 100644
--- a/conans/test/integration/command/test_profile.py
+++ b/conans/test/integration/command/test_profile.py
@@ -74,7 +74,7 @@ def test_shorthand_syntax():
             """)})
 
     tc.run("profile show -pr:a=pre -pr:a=mid -pr:a=post -f=json")
-    out = json.loads(tc.out)
+    out = json.loads(tc.stdout)
     assert out == {'build': {'build_env': '',
                              'conf': {},
                              'options': {},
diff --git a/conans/test/integration/conanfile/test_deploy_method.py b/conans/test/integration/conanfile/test_deploy_method.py
index 7579ec5c0..1c8a3f1ba 100644
--- a/conans/test/integration/conanfile/test_deploy_method.py
+++ b/conans/test/integration/conanfile/test_deploy_method.py
@@ -76,3 +76,53 @@ def test_deploy_local():
     # I can exclude the current consumer, it won't fail
     c.run("install . --deployer-package=!& --deployer-package=*")
     assert "Install finished successfully" in c.out
+
+
+def test_deploy_method_tool_requires():
+    # Test to validate https://github.com/conan-io/docs/issues/3649
+    c = TestClient()
+    tool = textwrap.dedent(r"""
+        import os
+        from conan import ConanFile
+        from conan.tools.files import save, chdir
+        class Pkg(ConanFile):
+            name = "tool"
+            version = "0.1"
+            type = "application"
+
+            def package(self):
+               with chdir(self, self.package_folder):
+                   echo = "@echo off\necho MYTOOL RUNNING!!"
+                   save(self, "mytool.bat", echo)
+                   save(self, "mytool.sh", echo)
+                   os.chmod("mytool.sh", 0o777)
+
+            def package_info(self):
+               self.buildenv_info.prepend_path("PATH", self.package_folder)
+        """)
+    conanfile = textwrap.dedent("""
+        import os
+        from conan import ConanFile
+        from conan.tools.env import VirtualBuildEnv
+        class Pkg(ConanFile):
+            name = "pkg"
+            version = "0.1"
+            tool_requires = "tool/0.1"
+            settings = "os"
+
+            def deploy(self):
+                self.output.info(f"DEPLOY {os.getcwd()}")
+                venv = VirtualBuildEnv(self)
+                with venv.vars().apply():
+                    ext = "bat" if self.settings.os == "Windows" else "sh"
+                    self.run(f"mytool.{ext}")
+        """)
+    c.save({"tool/conanfile.py": tool,
+            "pkg/conanfile.py": conanfile})
+
+    c.run("create tool")
+    c.run("create pkg")
+
+    # install can deploy all
+    c.run("install --requires=pkg/0.1 -c:b tools.graph:skip_binaries=False --deployer-package=*")
+    assert "MYTOOL RUNNING!!" in c.out
diff --git a/conans/test/integration/layout/test_layout_generate.py b/conans/test/integration/layout/test_layout_generate.py
index 7548f0788..0fdbbe8ef 100644
--- a/conans/test/integration/layout/test_layout_generate.py
+++ b/conans/test/integration/layout/test_layout_generate.py
@@ -125,3 +125,33 @@ def test_generate_build_folder_test_package():
             "test_package/conanfile.py": conanfile})
     c.run("create .")
     assert f"build_folder in test_package: True" in c.out
+
+
+class TestCustomTestPackage:
+    def test_custom_test_package(self):
+        c = TestClient(light=True)
+        conanfile = GenConanfile("pkg", "0.1").with_class_attribute('test_package_folder="mytest"')
+        c.save({"conanfile.py": conanfile,
+                "mytest/conanfile.py": GenConanfile().with_test("self.output.info('MYTEST!')"),
+                "mytest2/conanfile.py": GenConanfile().with_test("self.output.info('MYTEST2!')")})
+        c.run("create .")
+        assert "MYTEST!" in c.out
+        c.run("create . -tf=mytest2")
+        assert "MYTEST2!" in c.out
+
+    def test_custom_test_package_subfolder(self):
+        c = TestClient(light=True)
+        conanfile = GenConanfile("pkg", "0.1").with_class_attribute('test_package_folder="my/test"')
+        c.save({"pkg/conanfile.py": conanfile,
+                "pkg/my/test/conanfile.py": GenConanfile().with_test("self.output.info('MYTEST!')")})
+        c.run("create pkg")
+        assert "MYTEST!" in c.out
+
+    def test_custom_test_package_sibling(self):
+        c = TestClient(light=True)
+        conanfile = GenConanfile("pkg", "0.1").with_class_attribute(
+            'test_package_folder="../my/test"')
+        c.save({"pkg/conan/conanfile.py": conanfile,
+                "pkg/my/test/conanfile.py": GenConanfile().with_test("self.output.info('MYTEST!')")})
+        c.run("create pkg/conan")
+        assert "MYTEST!" in c.out
diff --git a/conans/test/integration/toolchains/google/test_bazeldeps.py b/conans/test/integration/toolchains/google/test_bazeldeps.py
index 62b605949..92ccd008d 100644
--- a/conans/test/integration/toolchains/google/test_bazeldeps.py
+++ b/conans/test/integration/toolchains/google/test_bazeldeps.py
@@ -880,3 +880,92 @@ def test_error_missing_bazel_build_files_in_build_context():
           "--build=example")
     c.assert_listed_require({"example/1.0": "Cache"})
     c.assert_listed_require({"example/1.0": "Cache"}, build=True)
+
+
+class TestBazelGenerationBuildContext:
+    """
+    https://github.com/conan-io/conan/issues/15764
+    """
+    def test_bazel_generate(self):
+        c = TestClient()
+        tool = textwrap.dedent("""
+            import os
+            from conan import ConanFile
+            from conan.tools.google import BazelDeps
+            class Example(ConanFile):
+                name = "tool"
+                version = "1.0"
+                requires = "wayland/1.0"
+                tool_requires = "wayland/1.0"
+                def generate(self):
+                    tc = BazelDeps(self)
+                    tc.build_context_activated = ["wayland", "dep"]
+                    tc.generate()
+                def build(self):
+                    # Build context
+                    assert os.path.exists(os.path.join("build-wayland", "BUILD.bazel"))
+                    # Revisit this after investigation -> build_req = conanfile.dependencies.direct_build
+                    # assert os.path.exists(os.path.join("build-dep", "BUILD.bazel"))
+                    # Host context
+                    assert os.path.exists(os.path.join("wayland", "BUILD.bazel"))
+                    assert os.path.exists(os.path.join("dep", "BUILD.bazel"))
+                """)
+        c.save({"dep/conanfile.py": GenConanfile("dep", "1.0").with_package_type("shared-library"),
+                "wayland/conanfile.py": GenConanfile("wayland", "1.0").with_requires("dep/1.0"),
+                "tool/conanfile.py": tool,
+                "app/conanfile.py": GenConanfile().with_tool_requires("tool/1.0")})
+        c.run("export dep")
+        c.run("export wayland")
+        c.run("export tool")
+        c.run("install app --build=missing")
+        assert "Install finished successfully" in c.out  # the asserts in build() didn't fail
+        # Now make sure we can actually build with build!=host context
+        c.run("install app -s:h build_type=Debug --build=missing")
+        assert "Install finished successfully" in c.out  # the asserts in build() didn't fail
+
+    def test_bazel_generate_components(self):
+        c = TestClient()
+        tool = textwrap.dedent("""
+            import os
+            from conan import ConanFile
+            from conan.tools.google import BazelDeps
+            class Example(ConanFile):
+                name = "tool"
+                version = "1.0"
+                requires = "wayland/1.0"
+                tool_requires = "wayland/1.0"
+                def generate(self):
+                    tc = BazelDeps(self)
+                    tc.build_context_activated = ["wayland", "dep"]
+                    tc.generate()
+                def build(self):
+                    # Build context
+                    assert os.path.exists(os.path.join("build-wayland", "BUILD.bazel"))
+                    # Revisit this after investigation -> build_req = conanfile.dependencies.direct_build
+                    # assert os.path.exists(os.path.join("build-dep", "BUILD.bazel"))
+                    # Host context
+                    assert os.path.exists(os.path.join("wayland", "BUILD.bazel"))
+                    assert os.path.exists(os.path.join("dep", "BUILD.bazel"))
+                """)
+        wayland = textwrap.dedent("""
+            from conan import ConanFile
+            class Pkg(ConanFile):
+                name = "wayland"
+                version = "1.0"
+                requires = "dep/1.0"
+                def package_info(self):
+                    self.cpp_info.components["client"].libs = []
+                    self.cpp_info.components["server"].libs = []
+            """)
+        c.save({"dep/conanfile.py": GenConanfile("dep", "1.0").with_package_type("shared-library"),
+                "wayland/conanfile.py": wayland,
+                "tool/conanfile.py": tool,
+                "app/conanfile.py": GenConanfile().with_tool_requires("tool/1.0")})
+        c.run("export dep")
+        c.run("export wayland")
+        c.run("export tool")
+        c.run("install app --build=missing")
+        assert "Install finished successfully" in c.out  # the asserts in build() didn't fail
+        # Now make sure we can actually build with build!=host context
+        c.run("install app -s:h build_type=Debug --build=missing")
+        assert "Install finished successfully" in c.out  # the asserts in build() didn't fail