Repository: conan-io/conan

Issue:
[feature] New AutotoolsDeps customization
The `AutotoolsDeps` generator should precalculate things in the constructor to allow the user to modify them before calling the `generate()` method.


Discussion:

Resolving patch:
diff --git a/conan/tools/env/environment.py b/conan/tools/env/environment.py
--- a/conan/tools/env/environment.py
+++ b/conan/tools/env/environment.py
@@ -67,6 +67,9 @@ def copy(self):
     def is_path(self):
         return self._path
 
+    def remove(self, value):
+        self._values.remove(value)
+
     def append(self, value, separator=None):
         if separator is not None:
             self._sep = separator
@@ -162,6 +165,9 @@ def prepend(self, name, value, separator=None):
     def prepend_path(self, name, value):
         self._values.setdefault(name, _EnvValue(name, path=True)).prepend(value)
 
+    def remove(self, name, value):
+        self._values[name].remove(value)
+
     def save_bat(self, filename, generate_deactivate=False, pathsep=os.pathsep):
         deactivate = textwrap.dedent("""\
             echo Capturing current environment in deactivate_{filename}
@@ -282,15 +288,9 @@ def get(self, name, default=None):
         return v.get_value(self._conanfile)
 
     def items(self):
+        """returns {str: str} (varname: value)"""
         return {k: v.get_value(self._conanfile) for k, v in self._values.items()}.items()
 
-    def var(self, name):
-        return self._values[name]
-
-    def var_items(self):
-        # Access to the dict items, so users can do what they want with the underlying env-var
-        return self._values.items()
-
     def __eq__(self, other):
         """
         :type other: Environment
diff --git a/conan/tools/gnu/autotoolsdeps.py b/conan/tools/gnu/autotoolsdeps.py
--- a/conan/tools/gnu/autotoolsdeps.py
+++ b/conan/tools/gnu/autotoolsdeps.py
@@ -6,60 +6,58 @@
 
 class AutotoolsDeps:
     def __init__(self, conanfile):
-        # Set the generic objects before mapping to env vars to let the user
-        # alter some value
         self._conanfile = conanfile
-        self._cpp_info = None
+        self._environment = None
         check_using_build_profile(self._conanfile)
 
-    @property
-    def cpp_info(self):
-        if self._cpp_info is None:
-            self._cpp_info = NewCppInfo()
-            for dep in self._conanfile.dependencies.host.values():
-                dep_cppinfo = dep.new_cpp_info.copy()
-                dep_cppinfo.set_relative_base_folder(dep.package_folder)
-                # In case we have components, aggregate them, we do not support isolated
-                # "targets" with autotools
-                dep_cppinfo.aggregate_components()
-                self._cpp_info.merge(dep_cppinfo)
-        return self._cpp_info
+    def _get_cpp_info(self):
+        ret = NewCppInfo()
+        for dep in self._conanfile.dependencies.host.values():
+            dep_cppinfo = dep.new_cpp_info.copy()
+            dep_cppinfo.set_relative_base_folder(dep.package_folder)
+            # In case we have components, aggregate them, we do not support isolated
+            # "targets" with autotools
+            dep_cppinfo.aggregate_components()
+            ret.merge(dep_cppinfo)
+        return ret
 
+    @property
     def environment(self):
-        flags = GnuDepsFlags(self._conanfile, self.cpp_info)
-
-        # cpp_flags
-        cpp_flags = []
-        cpp_flags.extend(flags.include_paths)
-        cpp_flags.extend(flags.defines)
-
-        # Ldflags
-        ldflags = flags.sharedlinkflags
-        ldflags.extend(flags.exelinkflags)
-        ldflags.extend(flags.frameworks)
-        ldflags.extend(flags.framework_paths)
-        ldflags.extend(flags.lib_paths)
-        # FIXME: Previously we had an argument "include_rpath_flags" defaulted to False
-        ldflags.extend(flags.rpath_flags)
-
-        # cflags
-        cflags = flags.cflags
-        cxxflags = flags.cxxflags
-
-        srf = flags.sysroot
-        if srf:
-            cflags.append(srf)
-            cxxflags.append(srf)
-            ldflags.append(srf)
-
-        env = Environment(self._conanfile)
-        env.append("CPPFLAGS", cpp_flags)
-        env.append("LIBS", flags.libs)
-        env.append("LDFLAGS", ldflags)
-        env.append("CXXFLAGS", cxxflags)
-        env.append("CFLAGS", cflags)
-        return env
-
-    def generate(self, env=None, auto_activate=True):
-        env = env or self.environment()
-        env.save_script("conanautotoolsdeps", auto_activate=auto_activate)
+        if self._environment is None:
+            flags = GnuDepsFlags(self._conanfile, self._get_cpp_info())
+
+            # cpp_flags
+            cpp_flags = []
+            cpp_flags.extend(flags.include_paths)
+            cpp_flags.extend(flags.defines)
+
+            # Ldflags
+            ldflags = flags.sharedlinkflags
+            ldflags.extend(flags.exelinkflags)
+            ldflags.extend(flags.frameworks)
+            ldflags.extend(flags.framework_paths)
+            ldflags.extend(flags.lib_paths)
+            # FIXME: Previously we had an argument "include_rpath_flags" defaulted to False
+            ldflags.extend(flags.rpath_flags)
+
+            # cflags
+            cflags = flags.cflags
+            cxxflags = flags.cxxflags
+
+            srf = flags.sysroot
+            if srf:
+                cflags.append(srf)
+                cxxflags.append(srf)
+                ldflags.append(srf)
+
+            env = Environment(self._conanfile)
+            env.append("CPPFLAGS", cpp_flags)
+            env.append("LIBS", flags.libs)
+            env.append("LDFLAGS", ldflags)
+            env.append("CXXFLAGS", cxxflags)
+            env.append("CFLAGS", cflags)
+            self._environment = env
+        return self._environment
+
+    def generate(self,  auto_activate=True):
+        self.environment.save_script("conanautotoolsdeps", auto_activate=auto_activate)


Test patch:
diff --git a/conans/test/unittests/client/toolchain/autotools/autotools_deps_test.py b/conans/test/unittests/client/toolchain/autotools/autotools_deps_test.py
--- a/conans/test/unittests/client/toolchain/autotools/autotools_deps_test.py
+++ b/conans/test/unittests/client/toolchain/autotools/autotools_deps_test.py
@@ -1,3 +1,5 @@
+import os
+import platform
 from collections import OrderedDict
 
 import mock
@@ -10,6 +12,7 @@
 from conans.model.dependencies import ConanFileDependencies, Requirement
 from conans.model.ref import ConanFileReference
 from conans.test.utils.mocks import MockSettings
+from conans.test.utils.test_files import temp_folder
 
 
 def get_cpp_info(name):
@@ -71,19 +74,35 @@ def test_foo():
              "compiler.version": "7.1",
              "cppstd": "17"})
         deps = AutotoolsDeps(consumer)
-        # deps.generate()
-        env = deps.environment()
-        assert env["LDFLAGS"] == 'dep1_shared_link_flag dep2_shared_link_flag ' \
+
+        env = deps.environment
+
+        # Customize the environment
+        env.remove("LDFLAGS", "dep2_shared_link_flag")
+        env.remove("LDFLAGS", "-F /path/to/folder_dep1/one/framework/path/dep1")
+        env.append("LDFLAGS", "OtherSuperStuff")
+
+        # The contents are of course modified
+        assert env["LDFLAGS"] == 'dep1_shared_link_flag ' \
                                  'dep1_exe_link_flag dep2_exe_link_flag ' \
                                  '-framework dep1_oneframework -framework dep1_twoframework ' \
                                  '-framework dep2_oneframework -framework dep2_twoframework ' \
-                                 '-F /path/to/folder_dep1/one/framework/path/dep1 ' \
                                  '-F /path/to/folder_dep2/one/framework/path/dep2 ' \
                                  '-L/path/to/folder_dep1/one/lib/path/dep1 ' \
                                  '-L/path/to/folder_dep2/one/lib/path/dep2 ' \
                                  '-Wl,-rpath,"/path/to/folder_dep1/one/lib/path/dep1" ' \
                                  '-Wl,-rpath,"/path/to/folder_dep2/one/lib/path/dep2" ' \
-                                 '--sysroot=/path/to/folder/dep1'
+                                 '--sysroot=/path/to/folder/dep1 OtherSuperStuff'
 
         assert env["CXXFLAGS"] == 'dep1_a_cxx_flag dep2_a_cxx_flag --sysroot=/path/to/folder/dep1'
         assert env["CFLAGS"] == 'dep1_a_c_flag dep2_a_c_flag --sysroot=/path/to/folder/dep1'
+        folder = temp_folder()
+        consumer.folders.set_base_install(folder)
+        deps.generate()
+        extension = ".bat" if platform.system() == "Windows" else ".sh"
+
+        # The generated file also contains the changes
+        with open(os.path.join(folder, "conanautotoolsdeps{}".format(extension))) as _f:
+            contents = _f.read()
+            assert "path/to/folder_dep1/one/framework/path/dep1" not in contents
+            assert "OtherSuperStuff" in contents
diff --git a/conans/test/unittests/tools/env/test_env.py b/conans/test/unittests/tools/env/test_env.py
--- a/conans/test/unittests/tools/env/test_env.py
+++ b/conans/test/unittests/tools/env/test_env.py
@@ -91,7 +91,7 @@ def test_compose_combinations(op1, v1, s1, op2, v2, s2, result):
     env.compose(env2)
     with environment_append({"MyVar": "MyVar"}):
         assert env.get("MyVar") == result
-    assert env.var("MyVar").get_str(conanfile, "{name}") == result
+    assert env._values["MyVar"].get_str(conanfile, "{name}") == result
 
 
 @pytest.mark.parametrize("op1, v1, op2, v2, result",
@@ -124,7 +124,7 @@ def test_compose_path_combinations(op1, v1, op2, v2, result):
     else:
         env2.unset("MyVar")
     env.compose(env2)
-    assert env.var("MyVar").get_str(conanfile, "{name}", pathsep=":") == result
+    assert env._values["MyVar"].get_str(conanfile, "{name}", pathsep=":") == result
 
 
 def test_profile():
@@ -374,3 +374,19 @@ def test_env_win_bash():
         content = f.read()
         assert 'MyVar="MyValue"' in content
         assert 'MyPath="/c/path/to/something:/d/otherpath"' in content
+
+
+def test_public_access():
+    env = Environment(conanfile)
+    env.define("MyVar", "MyValue")
+    env.append("MyVar", "MyValue2")
+    env.define_path("MyPath", "c:/path/to/something")
+    env.append_path("MyPath", "D:/Otherpath")
+    for name, values in env.items():
+        if name == "MyVar":
+            assert values == "MyValue MyValue2"
+        if name == "MyPath":
+            assert values == "c:/path/to/something{}D:/Otherpath".format(os.pathsep)
+
+    env.remove("MyPath", "D:/Otherpath")
+    assert env.get("MyPath") == "c:/path/to/something"