diff --git a/conan/tools/gnu/autotoolstoolchain.py b/conan/tools/gnu/autotoolstoolchain.py index 58115a6072b..3819de54d1d 100644 --- a/conan/tools/gnu/autotoolstoolchain.py +++ b/conan/tools/gnu/autotoolstoolchain.py @@ -7,21 +7,43 @@ from conan.tools.env import Environment from conan.tools.files.files import save_toolchain_args from conan.tools.gnu.get_gnu_triplet import _get_gnu_triplet -from conan.tools.microsoft import VCVars, is_msvc, msvc_runtime_flag +from conan.tools.microsoft import VCVars, msvc_runtime_flag from conans.errors import ConanException from conans.tools import args_to_string +# FIXME: Remove this whenever self.xxx_args disappear +def _args_to_dict(args): + """ + Given a list of arguments as GNU options, it'll return the same content but as a + dict-like Python object, e.g., {"--flag_name": "flag_value"} + """ + ret = {} + for flag in args: + # Only splitting if "=" is there. No need to check if it starts by "--" or "-" + option = flag.split("=") + if len(option) == 2: + ret[option[0]] = option[1] + else: + ret[option[0]] = "" + return ret + + +def _options_to_string(options): + opts = [] + for k, v in options.items(): + # If value, it's assumed the flag will be flag=value, else keeping flag only + # For instance: {"--opt1": "whatever", "-abc": ""} --> ["--opt1=whatever", "-abc"] + opts.append(f"{k}={v}" if v else k) + return args_to_string(opts) + + class AutotoolsToolchain: def __init__(self, conanfile, namespace=None, prefix="/"): self._conanfile = conanfile self._namespace = namespace self._prefix = prefix - self.configure_args = self._default_configure_shared_flags() + self._default_configure_install_flags() - self.autoreconf_args = self._default_autoreconf_flags() - self.make_args = [] - # Flags self.extra_cxxflags = [] self.extra_cflags = [] @@ -55,12 +77,13 @@ def __init__(self, conanfile, namespace=None, prefix="/"): self.sysroot_flag = None if cross_building(self._conanfile): + # Host triplet os_build, arch_build, os_host, arch_host = get_cross_building_settings(self._conanfile) compiler = self._conanfile.settings.get_safe("compiler") if not self._host: self._host = _get_gnu_triplet(os_host, arch_host, compiler=compiler) + # Build triplet self._build = _get_gnu_triplet(os_build, arch_build, compiler=compiler) - # Apple Stuff if os_build == "Macos": sdk_path = apple_sdk_path(conanfile) @@ -73,6 +96,18 @@ def __init__(self, conanfile, namespace=None, prefix="/"): sysroot = self._conanfile.conf.get("tools.build:sysroot") sysroot = sysroot.replace("\\", "/") if sysroot is not None else None self.sysroot_flag = "--sysroot {}".format(sysroot) if sysroot else None + # DEPRECATED: self.xxxx_args attributes in favor of self.xxxxx_options ones since Conan 1.57 + self.configure_args = self._default_configure_shared_flags() + \ + self._default_configure_install_flags() + \ + self._get_triplets() + self.autoreconf_args = self._default_autoreconf_flags() + self.make_args = [] + # FIXME: Remove this whenever self.xxx_args are not used anymore + self.use_new_options = False + # New dict-like attributes since Conan 1.57 + self.configure_options = _args_to_dict(self.configure_args) + self.autoreconf_options = _args_to_dict(self.autoreconf_args) + self.make_options = _args_to_dict(self.make_args) check_using_build_profile(self._conanfile) @@ -178,19 +213,27 @@ def _get_argument(argument_name, cppinfo_name): _get_argument("datarootdir", "resdirs")]) return [el for el in configure_install_flags if el] - def _default_autoreconf_flags(self): + @staticmethod + def _default_autoreconf_flags(): return ["--force", "--install"] - def generate_args(self): - configure_args = [] - configure_args.extend(self.configure_args) - user_args_str = args_to_string(self.configure_args) - for flag, var in (("host", self._host), ("build", self._build), ("target", self._target)): - if var and flag not in user_args_str: - configure_args.append('--{}={}'.format(flag, var)) - - args = {"configure_args": args_to_string(configure_args), - "make_args": args_to_string(self.make_args), - "autoreconf_args": args_to_string(self.autoreconf_args)} + def _get_triplets(self): + triplets = [] + for flag, value in (("--host=", self._host), ("--build=", self._build), + ("--target=", self._target)): + if value: + triplets.append(f'{flag}{value}') + return triplets + def generate_args(self): + # FIXME: Remove this whenever self.xxx_args disappear + if self.use_new_options is False: + # Copying again the legacy args just in case recipes add/remove new ones + self.configure_options = _args_to_dict(self.configure_args) + self.make_options = _args_to_dict(self.make_args) + self.autoreconf_options = _args_to_dict(self.autoreconf_args) + + args = {"configure_args": _options_to_string(self.configure_options), + "make_args": _options_to_string(self.make_options), + "autoreconf_args": _options_to_string(self.autoreconf_options)} save_toolchain_args(args, namespace=self._namespace) diff --git a/conans/test/unittests/tools/gnu/autotoolschain_test.py b/conans/test/unittests/tools/gnu/autotoolschain_test.py index 93b7d31eca0..cf4343dbb8b 100644 --- a/conans/test/unittests/tools/gnu/autotoolschain_test.py +++ b/conans/test/unittests/tools/gnu/autotoolschain_test.py @@ -1,11 +1,31 @@ +from unittest.mock import patch + import pytest from conan.tools.gnu import AutotoolsToolchain +from conan.tools.gnu.autotoolstoolchain import _options_to_string from conans.errors import ConanException from conans.model.conf import Conf from conans.test.utils.mocks import ConanFileMock, MockSettings +@pytest.fixture() +def cross_building_conanfile(): + settings_build = MockSettings({"os": "Linux", + "arch": "x86_64", + "compiler": "gcc", + "compiler.version": "11", + "compiler.libcxx": "libstdc++", + "build_type": "Release"}) + settings_target = MockSettings({"os": "Android", "arch": "armv8"}) + settings = MockSettings({"os": "Emscripten", "arch": "wasm"}) + conanfile = ConanFileMock() + conanfile.settings = settings + conanfile.settings_build = settings_build + conanfile.settings_target = settings_target + return conanfile + + def test_get_gnu_triplet_for_cross_building(): """ Testing AutotoolsToolchain and _get_gnu_triplet() function in case of @@ -125,3 +145,46 @@ def test_compilers_mapping(): env = autotoolschain.environment().vars(conanfile) for compiler, env_var in autotools_mapping.items(): assert env[env_var] == f"path_to_{compiler}" + + +@patch("conan.tools.gnu.autotoolstoolchain.save_toolchain_args") +def test_check_configure_args_overwriting_and_deletion(save_args, cross_building_conanfile): + # Issue: https://github.com/conan-io/conan/issues/12642 + at = AutotoolsToolchain(cross_building_conanfile) + at.configure_args.extend([ + "--with-cross-build=my_path", + "--something-host=my_host" + ]) + at.generate_args() + configure_args = save_args.call_args[0][0]['configure_args'] + assert "--build=x86_64-linux-gnu" in configure_args + assert "--host=wasm32-local-emscripten" in configure_args + assert "--with-cross-build=my_path" in configure_args + assert "--something-host=my_host" in configure_args + # https://github.com/conan-io/conan/issues/12431 + at.configure_args.remove("--build=x86_64-linux-gnu") + at.configure_args.remove("--host=wasm32-local-emscripten") + at.generate_args() + configure_args = save_args.call_args[0][0]['configure_args'] + assert "--build=x86_64-linux-gnu" not in configure_args # removed + assert "--host=wasm32-local-emscripten" not in configure_args # removed + assert "--with-cross-build=my_path" in configure_args + assert "--something-host=my_host" in configure_args + + +def test_update_or_prune_any_args(cross_building_conanfile): + at = AutotoolsToolchain(cross_building_conanfile) + at.use_new_options = True + at.configure_options["--prefix"] = "/my/other/prefix" + at.configure_options.pop("--build") + at.configure_options.pop("--host") + configure_options = _options_to_string(at.configure_options) + assert "--prefix=/my/other/prefix" in configure_options + assert "--host=" not in configure_options + assert "--build=" not in configure_options + at.autoreconf_options.pop("--force") + autoreconf_options = _options_to_string(at.autoreconf_options) + assert "--force" not in autoreconf_options + at.make_options["--complex-flag"] = "new-value" + make_options = _options_to_string(at.make_options) + assert "--complex-flag=new-value" in make_options