From 9f023c38296a731e552f9a1f1ff787da644a6d2d Mon Sep 17 00:00:00 2001 From: James Date: Thu, 14 Dec 2023 15:48:28 +0100 Subject: [PATCH] proposal for source() buildenv discussion (#15153) * proposal for source() buildenv discussion * wip, failing * trying alternative opt-in class attribute * fix * wip * wip * add cmake_layout test * wip * named auto_generate --- conan/api/subapi/local.py | 2 + conan/tools/env/virtualbuildenv.py | 33 ++-- conan/tools/meson/toolchain.py | 6 +- conans/client/source.py | 8 +- .../profile_build_requires_test.py | 2 + .../test_build_requires_source_method.py | 146 ++++++++++++++++++ 6 files changed, 177 insertions(+), 20 deletions(-) create mode 100644 conans/test/integration/build_requires/test_build_requires_source_method.py diff --git a/conan/api/subapi/local.py b/conan/api/subapi/local.py index 8856f9671a7..45093c07cb4 100644 --- a/conan/api/subapi/local.py +++ b/conan/api/subapi/local.py @@ -83,6 +83,8 @@ def source(self, path, name=None, version=None, user=None, channel=None, remotes conanfile.folders.set_base_source(folder) conanfile.folders.set_base_export_sources(folder) conanfile.folders.set_base_recipe_metadata(os.path.join(folder, "metadata")) + # The generators are needed for the "conan source" local case with tool-requires + conanfile.folders.set_base_generators(folder) conanfile.folders.set_base_build(None) conanfile.folders.set_base_package(None) diff --git a/conan/tools/env/virtualbuildenv.py b/conan/tools/env/virtualbuildenv.py index b97e1beac21..cdc80f4dd5e 100644 --- a/conan/tools/env/virtualbuildenv.py +++ b/conan/tools/env/virtualbuildenv.py @@ -8,25 +8,32 @@ class VirtualBuildEnv: .bat or .sh script """ - def __init__(self, conanfile): + def __init__(self, conanfile, auto_generate=False): self._conanfile = conanfile - self._conanfile.virtualbuildenv = False + if not auto_generate: + self._conanfile.virtualbuildenv = False self.basename = "conanbuildenv" - # TODO: Make this use the settings_build - self.configuration = conanfile.settings.get_safe("build_type") - if self.configuration: - self.configuration = self.configuration.lower() - self.arch = conanfile.settings.get_safe("arch") - if self.arch: - self.arch = self.arch.lower() + self.configuration = None + self.arch = None @property def _filename(self): + if not self.configuration: + # TODO: Make this use the settings_build + configuration = self._conanfile.settings.get_safe("build_type") + configuration = configuration.lower() if configuration else None + else: + configuration = self.configuration + if not self.arch: + arch = self._conanfile.settings.get_safe("arch") + arch = arch.lower() if arch else None + else: + arch = self.arch f = self.basename - if self.configuration: - f += "-" + self.configuration.replace(".", "_") - if self.arch: - f += "-" + self.arch.replace(".", "_") + if configuration: + f += "-" + configuration.replace(".", "_") + if arch: + f += "-" + arch.replace(".", "_") return f def environment(self): diff --git a/conan/tools/meson/toolchain.py b/conan/tools/meson/toolchain.py index 4880e973437..291dc54b3af 100644 --- a/conan/tools/meson/toolchain.py +++ b/conan/tools/meson/toolchain.py @@ -182,11 +182,7 @@ def __init__(self, conanfile, backend=None): compilers_by_conf = self._conanfile.conf.get("tools.build:compiler_executables", default={}, check_type=dict) # Read the VirtualBuildEnv to update the variables - # FIXME: This VirtualBuildEnv instance is breaking things!! - # FIXME: It shouldn't be used here, not intended for this use case - prev_status = self._conanfile.virtualbuildenv - build_env = VirtualBuildEnv(self._conanfile).vars() - self._conanfile.virtualbuildenv = prev_status + build_env = VirtualBuildEnv(self._conanfile, auto_generate=True).vars() #: Sets the Meson ``c`` variable, defaulting to the ``CC`` build environment value. #: If provided as a blank-separated string, it will be transformed into a list. #: Otherwise, it remains a single string. diff --git a/conans/client/source.py b/conans/client/source.py index fac2037eee4..3fee67d0319 100644 --- a/conans/client/source.py +++ b/conans/client/source.py @@ -1,6 +1,7 @@ import os from conan.api.output import ConanOutput +from conan.tools.env import VirtualBuildEnv from conans.errors import ConanException, conanfile_exception_formatter, NotFoundException, \ conanfile_remove_attr from conans.util.files import (is_dirty, mkdir, rmdir, set_dirty_context_manager, @@ -67,8 +68,11 @@ def config_source(export_source_folder, conanfile, hook_manager): # First of all get the exported scm sources (if auto) or clone (if fixed) # Now move the export-sources to the right location merge_directories(export_source_folder, conanfile.folders.base_source) - - run_source_method(conanfile, hook_manager) + if getattr(conanfile, "source_buildenv", True): + with VirtualBuildEnv(conanfile, auto_generate=True).vars().apply(): + run_source_method(conanfile, hook_manager) + else: + run_source_method(conanfile, hook_manager) def run_source_method(conanfile, hook_manager): diff --git a/conans/test/integration/build_requires/profile_build_requires_test.py b/conans/test/integration/build_requires/profile_build_requires_test.py index a0529a043f7..9a33d0b8c53 100644 --- a/conans/test/integration/build_requires/profile_build_requires_test.py +++ b/conans/test/integration/build_requires/profile_build_requires_test.py @@ -233,6 +233,8 @@ def test_tool_requires_revision_profile(): # We shoul be able to explicitly [tool_require] a recipe revision in the profile c = TestClient() build_profile = textwrap.dedent("""\ + [settings] + os=Linux [tool_requires] *:tool/0.1#2d65f1b4af1ce59028f96adbfe7ed5a2 """) diff --git a/conans/test/integration/build_requires/test_build_requires_source_method.py b/conans/test/integration/build_requires/test_build_requires_source_method.py new file mode 100644 index 00000000000..679371d828d --- /dev/null +++ b/conans/test/integration/build_requires/test_build_requires_source_method.py @@ -0,0 +1,146 @@ +import textwrap + +import pytest + +from conans.test.utils.tools import TestClient + + +class TestBuildEnvSource: + @pytest.fixture() + def client(self): + c = TestClient() + tool = textwrap.dedent(r""" + import os + from conan import ConanFile + from conan.tools.files import chdir, save + + class Tool(ConanFile): + name = "tool" + version = "0.1" + def package(self): + with chdir(self, self.package_folder): + echo = f"@echo off\necho MY-TOOL! {self.name}/{self.version}!!" + save(self, "bin/mytool.bat", echo) + save(self, "bin/mytool.sh", echo) + os.chmod("bin/mytool.sh", 0o777) + """) + c.save({"conanfile.py": tool}) + c.run("create .") + return c + + def test_source_buildenv(self, client): + c = client + pkg = textwrap.dedent(""" + from conan import ConanFile + import platform + + class Pkg(ConanFile): + name = "pkg" + version = "0.1" + tool_requires = "tool/0.1" + + def source(self): + cmd = "mytool.bat" if platform.system() == "Windows" else "mytool.sh" + self.run(cmd) + """) + c.save({"conanfile.py": pkg}) + c.run("create .") + assert "MY-TOOL! tool/0.1" in c.out + + c.run("install .") # to generate conanbuild script first, so it is available + c.run("source .") + assert "MY-TOOL! tool/0.1" in c.out + + def test_source_buildenv_layout(self, client): + c = client + pkg = textwrap.dedent(""" + from conan import ConanFile + import platform + + class Pkg(ConanFile): + name = "pkg" + version = "0.1" + tool_requires = "tool/0.1" + settings = "build_type" + + def layout(self): + self.folders.source = "mysrc" + bt = self.settings.get_safe("build_type") or "Release" + self.folders.generators = f"mybuild{bt}" + + def source(self): + cmd = "mytool.bat" if platform.system() == "Windows" else "mytool.sh" + self.run(cmd) + """) + c.save({"conanfile.py": pkg}) + c.run("create .") + assert "MY-TOOL! tool/0.1" in c.out + + c.run("install .") # to generate conanbuild script first, so it is available + # But they are in a different folder, user can copy them to source folder to make + # them available to source() method. This works + # shutil.copytree(os.path.join(c.current_folder, "mybuild"), + # os.path.join(c.current_folder, "mysrc")) + # Another possibility is user directly calling the "conanbuild" script to activate + # The current solution defines "generators" to be robust for "conan source" command + # defaulting to "Release" config + c.run("source .") + assert "MY-TOOL! tool/0.1" in c.out + + def test_source_buildenv_cmake_layout(self, client): + c = client + pkg = textwrap.dedent(""" + import os + from conan import ConanFile + from conan.tools.cmake import cmake_layout + import platform + + class Pkg(ConanFile): + name = "pkg" + version = "0.1" + tool_requires = "tool/0.1" + settings = "build_type" + + def layout(self): + cmake_layout(self) + bt = self.settings.get_safe("build_type") or "Release" + self.folders.generators = os.path.join(bt, "generators") + + def source(self): + cmd = "mytool.bat" if platform.system() == "Windows" else "mytool.sh" + self.run(cmd) + """) + c.save({"conanfile.py": pkg}) + c.run("create .") + assert "MY-TOOL! tool/0.1" in c.out + + c.run("install .") + c.run("source .") + assert "MY-TOOL! tool/0.1" in c.out + + def test_source_buildenv_optout(self, client): + c = client + + pkg = textwrap.dedent(""" + from conan import ConanFile + import platform + + class Pkg(ConanFile): + name = "pkg" + version = "0.1" + tool_requires = "tool/0.1" + + source_buildenv = False + + def source(self): + cmd = "mytool.bat" if platform.system() == "Windows" else "mytool.sh" + self.run(cmd) + """) + c.save({"conanfile.py": pkg}) + c.run("create .", assert_error=True) + assert "ERROR: pkg/0.1: Error in source() method, line 14" in c.out + + # Local will still work, because ``install`` generates env-scripts and no layout + c.run("install .") + c.run("source .") + assert "MY-TOOL! tool/0.1" in c.out