Repository: conan-io/conan
Pull request: New AutotoolsDeps, AutotoolsToolchain helpers in conan.tools.gnu
Changelog: Feature: New AutotoolsDeps, AutotoolsToolchain helpers in conan.tools.gnu
Docs: https://github.com/conan-io/docs/pull/2057


Close https://github.com/conan-io/conan/issues/7070 (will invert the logic in _GLIBCXX_USE_CXX11_ABI)



Problem statement:
[bug] Invert the logic of _GLIBCXX_USE_CXX11_ABI ?
<!--
  Please don't forget to update the issue title.
  Include all applicable information to help us reproduce your problem.

  To help us debug your issue please explain:
-->

### Environment Details (include every applicable attribute)
  * Operating System+version: Ubuntu 18.04
  * Compiler+version: gcc9
  * Conan version: 1.25.1
  * Python version: 3.6.9

### Steps to reproduce (Include if Applicable)

The default for _GLIBCXX_USE_CXX11_ABI is 1 for several years now. Can we invert the logic of compiler.libcxx to use libstdc++ancient for the older version and use libstdc++ by default, or have libstdc++ _NOT_ emit _GLIBCXX_USE_CXX11_ABI ?

The reason I'm asking this is that we have a rule for formatting global macros in clang-tidy which is tripped by this macro, and there is no way to suppress it in tidy, since it does not come from a source file, but it is injected on the command line.

I suppose this is a bug in tidy, but there is some redundancy in Conan / CMake injecting this variable explicitly when it is not needed for quite some time now.

Hints from issue discussion:
Hi @0x8000-0000 

> The default for _GLIBCXX_USE_CXX11_ABI is 1 for several years now. Can we invert the logic of compiler.libcxx to use libstdc++ancient for the older version and use libstdc++ by default, or have libstdc++ NOT emit _GLIBCXX_USE_CXX11_ABI ?

I am not sure I understood that. Is that you are using the default auto-detected profile that is using ``compiler.libcxx=libstdc++``?

If that is the case, this is not a bug, this is by design, and cannot be changed without breaking. The reason it is this way is because of wider binary compatibility with older distros, even modern compilers can't upgrade to libstdc++11 in older distros: https://blog.conan.io/2016/03/22/From-CMake-syntax-to-libstdc++-ABI-incompatibiliy-migrations-are-always-hard.html

Using the default profile is not recommended for production. Better use your own profiles (can manage them with ``conan config install``), with the ``compiler.libcxx=libstdc++11`` value. Or if not, dynamically change the default one with ``conan profile update compiler.libcxx=libstdc++11 default``.

Please let me know if that helps, most likely I didn't understand this well enough.
I have compiler.libcxx=libstdc++11 in my profile, and it injects the definition on the command line when building, even though it is redundant given the compiler version I'm using.

The [dual ABI](https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html) page says:

> Using the default configuration options for GCC the default value of the macro is 1 which causes the new ABI to be active, so to use the old ABI you must explicitly define the macro to 0 before including any library headers. (Be aware that some GNU/Linux distributions configure GCC 5 differently so that the default value of the macro is 0 and users must define it to 1 to enable the new ABI.) 


So since it was introduced, on most distributions the value was 1.

What I am asking in effect is that for `compiler.libcxx=libstdc++11` with GCC9 we should not get a define, and for `compiler.libcxx=libstdc++` with GCC9 we should get -D_GLIBCXX_USE_CXX11_ABI=0, otherwise we'll keep adding this define until the end of time ;)
This is the bug in clang-tidy: https://bugs.llvm.org/show_bug.cgi?id=42635
The problem I see with this is that it may not be evident what is the default value for all the compilers we are supporting including all the versions.

Currently, we adjusting `_GLIBCXX_USE_CXX11_ABI=0` or `_GLIBCXX_USE_CXX11_ABI=1` for compilers `gcc`, `clang` & `apple-clang`

@0x8000-0000 do you know if we can find this information in the GNU website?
> The problem I see with this is that it may not be evident what is the default value for all the compilers we are supporting including all the versions.
> 
> Currently, we adjusting `_GLIBCXX_USE_CXX11_ABI=0` or `_GLIBCXX_USE_CXX11_ABI=1` for compilers `gcc`, `clang` & `apple-clang`
> 
> @0x8000-0000 do you know if we can find this information in the GNU website?

I don't know, and as documented by the page I linked possible that distributions are setting up the default differently than what upstream has.

The way that the configuration setting was defined in Conan, it is not clear what the plan for sunsetting it is. GCC5 was released 5 years ago, and when it was release the default was to use the new ABI. Perhaps this should have been followed here and users should have had to explicitly select the old ABI, and by default to follow what the compiler is doing by default.

With the current semantic of compiler.libcxx, I am forced to make a choice, and it is possible I'll make the wrong choice.
@0x8000-0000 I understand the hassle of being forced to set `compiler.libcxx` and it is something you don't worry about in a small project if you are not using Conan. However, as Conan manages the binary compatibility I think this is something to really take into account.

With the lack of information about the defaults, I find it difficult to rely on just the defaults of the compiler and I consider changing this a bit risky for the value. @uilianries @SSE4 WDYT about this topic?
For gcc you can find out if your compiler was built with `_GLIBCXX_USE_CXX11_ABI=0` by running `gcc -v` and looking for `--with-default-libstdcxx-abi=gcc4-compatible`.  For example:
```
$ gcc --version
gcc (GCC) 5.3.1 20160406 (Red Hat 5.3.1-6)

$ gcc -v 2>&1 | sed -n 's/.*\(--with-default-libstdcxx-abi=gcc4-compatible\).*/\1/p'
--with-default-libstdcxx-abi=gcc4-compatible
```

The Red Hat Devtoolset compiler suites continue to use `_GLIBCXX_USE_CXX11_ABI=0` all the way up to gcc 8 for backwards compatibility.  For example:
```
$ gcc --version
gcc (GCC) 8.3.1 20190311 (Red Hat 8.3.1-3)

$ gcc -v 2>&1 | sed -n 's/.*\(--with-default-libstdcxx-abi=gcc4-compatible\).*/\1/p'
--with-default-libstdcxx-abi=gcc4-compatible
```

Devtoolset is very common in Red Hat Enterprise Linux shops. 
Thanks a lot for the info @sourcedelica. I still think it is too much for the value and a bit risky to change the definition for the benefit (just a clang-tidy warning, or am I missing something?).

I can only think about something like #5740 to improve this.
> The way that the configuration setting was defined in Conan, it is not clear what the plan for sunsetting it is. GCC5 was released 5 years ago, and when it was release the default was to use the new ABI. Perhaps this should have been followed here and users should have had to explicitly select the old ABI, and by default to follow what the compiler is doing by default.

This is not very accurate, and much part of the problem. Specifically this: ``GCC5 was released 5 years ago, and when it was release the default was to use the new ABI.``

GCC5 and even more modern versions, when used in a somewhat older distro, still defaults to ``libstdc++``, not to ``libstdc++11``. This is very unfortunate, but it is the reality, and nothing Conan can do about, but to model the choice of ``libcxx``: https://blog.conan.io/2016/03/22/From-CMake-syntax-to-libstdc++-ABI-incompatibiliy-migrations-are-always-hard.html

> With the current semantic of compiler.libcxx, I am forced to make a choice, and it is possible I'll make the wrong choice.

Yes, as explained above, because it is not a constant default, it really changes from platform to platform, distro to distro, so the only way is to making it a choice.

Summary: I agree with @danimtb, definitely this is too much of a risk. Conan not passing _GLIBCXX_USE_CXX11_ABI will cause many ABI incompatibilities for many compilers, and at the moment it is almost impossible to know which compiler in which distros have this as the default or not. As the request is related to a warning of clang-tidy, it doesn't seem worth the risk (almost a certainty) of breaking hundreds of Conan users.
Hi @danimtb - I’m not looking for a change. I agree with the current approach. I was just providing a datapoint - that the old ABI is still in use and probably will be indefinitely on Red Hat Enterprise.

Commit message: fix adjust path

Patch to review:
diff --git a/.gitignore b/.gitignore
index 2e4f6277c3e..4a27bb1ea69 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,7 +7,6 @@ __pycache__/
 
 # Distribution / packaging
 .Python
-env/
 build/
 develop-eggs/
 dist/
diff --git a/conan/tools/_compilers.py b/conan/tools/_compilers.py
index b1eaf5ec587..199eb456eb0 100644
--- a/conan/tools/_compilers.py
+++ b/conan/tools/_compilers.py
@@ -39,3 +39,58 @@ def architecture_flag(settings):
                 "e2k-v6": "-march=elbrus-v6",
                 "e2k-v7": "-march=elbrus-v7"}.get(str(arch), "")
     return ""
+
+
+def build_type_flags(settings):
+    """
+    returns flags specific to the build type (Debug, Release, etc.)
+    (-s, -g, /Zi, etc.)
+    """
+    compiler = settings.get_safe("compiler.base") or settings.get_safe("compiler")
+
+    build_type = settings.get_safe("build_type")
+    vs_toolset = settings.get_safe("compiler.toolset")
+    if not compiler or not build_type:
+        return ""
+
+    # https://github.com/Kitware/CMake/blob/d7af8a34b67026feaee558433db3a835d6007e06/
+    # Modules/Platform/Windows-MSVC.cmake
+    if str(compiler) == 'Visual Studio':
+        if vs_toolset and "clang" in str(vs_toolset):
+            flags = {"Debug": ["-gline-tables-only", "-fno-inline", "-O0"],
+                     "Release": ["-O2"],
+                     "RelWithDebInfo": ["-gline-tables-only", "-O2", "-fno-inline"],
+                     "MinSizeRel": []
+                     }.get(build_type, ["-O2", "-Ob2"])
+        else:
+            flags = {"Debug": ["-Zi", "-Ob0", "-Od"],
+                     "Release": ["-O2", "-Ob2"],
+                     "RelWithDebInfo": ["-Zi", "-O2", "-Ob1"],
+                     "MinSizeRel": ["-O1", "-Ob1"],
+                     }.get(build_type, [])
+        return flags
+    else:
+        # https://github.com/Kitware/CMake/blob/f3bbb37b253a1f4a26809d6f132b3996aa2e16fc/
+        # Modules/Compiler/GNU.cmake
+        # clang include the gnu (overriding some things, but not build type) and apple clang
+        # overrides clang but it doesn't touch clang either
+        if str(compiler) in ["clang", "gcc", "apple-clang", "qcc", "mcst-lcc"]:
+            # FIXME: It is not clear that the "-s" is something related with the build type
+            # cmake is not adjusting it
+            # -s: Remove all symbol table and relocation information from the executable.
+            flags = {"Debug": ["-g"],
+                     "Release": ["-O3", "-s"] if str(compiler) == "gcc" else ["-O3"],
+                     "RelWithDebInfo": ["-O2", "-g"],
+                     "MinSizeRel": ["-Os"],
+                     }.get(build_type, [])
+            return flags
+        elif str(compiler) == "sun-cc":
+            # https://github.com/Kitware/CMake/blob/f3bbb37b253a1f4a26809d6f132b3996aa2e16fc/
+            # Modules/Compiler/SunPro-CXX.cmake
+            flags = {"Debug": ["-g"],
+                     "Release": ["-xO3"],
+                     "RelWithDebInfo": ["-xO2", "-g"],
+                     "MinSizeRel": ["-xO2", "-xspace"],
+                     }.get(build_type, [])
+            return flags
+    return ""
diff --git a/conan/tools/cmake/utils.py b/conan/tools/cmake/utils.py
index 7f572daf165..76513909c88 100644
--- a/conan/tools/cmake/utils.py
+++ b/conan/tools/cmake/utils.py
@@ -1,7 +1,6 @@
 import os
 
 from conans.errors import ConanException
-from conans.util.log import logger
 
 
 def is_multi_configuration(generator):
@@ -33,22 +32,7 @@ def get_generator(conanfile):
         return base
 
     compiler_base = conanfile.settings.get_safe("compiler.base")
-    arch = conanfile.settings.get_safe("arch")
-
     compiler_base_version = conanfile.settings.get_safe("compiler.base.version")
-    if hasattr(conanfile, 'settings_build'):
-        os_build = conanfile.settings_build.get_safe('os')
-    else:
-        os_build = conanfile.settings.get_safe('os_build')
-    if os_build is None:  # Assume is the same specified in host settings, not cross-building
-        os_build = conanfile.settings.get_safe("os")
-
-    if not compiler or not compiler_version or not arch:
-        if os_build == "Windows":
-            logger.warning("CMake generator could not be deduced from settings")
-            return None
-        return "Unix Makefiles"
-
     if compiler == "Visual Studio" or compiler_base == "Visual Studio":
         version = compiler_base_version or compiler_version
         major_version = version.split('.', 1)[0]
@@ -59,12 +43,22 @@ def get_generator(conanfile):
                     '12': '12 2013',
                     '14': '14 2015',
                     '15': '15 2017',
-                    '16': '16 2019'}.get(major_version, "UnknownVersion %s" % version)
+                    '16': '16 2019'}.get(major_version)
         base = "Visual Studio %s" % _visuals
         return base
 
-    # The generator depends on the build machine, not the target
-    if os_build == "Windows" and compiler != "qcc":
-        return "MinGW Makefiles"  # it is valid only under Windows
+    if hasattr(conanfile, 'settings_build'):
+        os_build = conanfile.settings_build.get_safe('os')
+    else:
+        os_build = conanfile.settings.get_safe('os_build')
+    if os_build is None:  # Assume is the same specified in host settings, not cross-building
+        os_build = conanfile.settings.get_safe("os")
+
+    if os_build == "Windows":
+        sub = conanfile.settings.get_safe("os.subsystem")
+        if sub in ("cygwin", "msys2", "msys") or compiler == "qcc":
+            return "Unix Makefiles"
+        else:
+            return "MinGW Makefiles"
 
     return "Unix Makefiles"
diff --git a/conan/tools/env/__init__.py b/conan/tools/env/__init__.py
new file mode 100644
index 00000000000..7656f772c0d
--- /dev/null
+++ b/conan/tools/env/__init__.py
@@ -0,0 +1 @@
+from conan.tools.env.environment import Environment
diff --git a/conan/tools/env/environment.py b/conan/tools/env/environment.py
new file mode 100644
index 00000000000..4ee2952c062
--- /dev/null
+++ b/conan/tools/env/environment.py
@@ -0,0 +1,138 @@
+import textwrap
+from collections import OrderedDict
+
+from conans.util.files import save
+
+PLACEHOLDER = "$CONANVARPLACEHOLDER%"
+
+
+class EnvironmentItem:
+
+    def __init__(self, value=None, separator=None):
+        self._value = value
+        self._separator = separator
+
+    def value(self, placeholder):
+        value = [v if v != PLACEHOLDER else placeholder for v in self._value]
+        value = self._separator.join(value) if value else ""
+        return value
+
+    def copy(self):
+        return EnvironmentItem(self._value[:], self._separator)
+
+    def define(self, value, separator=" "):
+        self._value = value if isinstance(value, list) else [value]
+        self._separator = separator
+
+    def append(self, value, separator=" "):
+        value = value if isinstance(value, list) else [value]
+        self._value = [PLACEHOLDER] + value
+        self._separator = separator
+
+    def prepend(self, value, separator=" "):
+        value = value if isinstance(value, list) else [value]
+        self._value = value + [PLACEHOLDER]
+        self._separator = separator
+
+    def clean(self):
+        self._value = []
+        self._separator = None
+
+    def update(self, other):
+        """
+       :type other: EnvironmentItem
+       """
+        result = other._value
+        try:
+            index = result.index(PLACEHOLDER)
+            result[index:index+1] = self._value
+            assert self._separator == other._separator
+        except ValueError:
+            pass
+        self._value = result
+        self._separator = other._separator
+
+
+class Environment:
+    def __init__(self):
+        # It being ordered allows for Windows case-insensitive composition
+        self._values = OrderedDict()
+
+    def __getitem__(self, name):
+        return self._values.setdefault(name, EnvironmentItem())
+
+    def save_bat(self, filename, generate_deactivate=True):
+        deactivate = textwrap.dedent("""\
+            echo Capturing current environment in deactivate_{filename}
+            setlocal
+            echo @echo off > "deactivate_{filename}"
+            echo echo Restoring environment >> "deactivate_{filename}"
+            for %%v in ({vars}) do (
+                set foundenvvar=
+                for /f "delims== tokens=1,2" %%a in ('set') do (
+                    if "%%a" == "%%v" (
+                        echo set %%a=%%b>> "deactivate_{filename}"
+                        set foundenvvar=1
+                    )
+                )
+                if not defined foundenvvar (
+                    echo set %%v=>> "deactivate_{filename}"
+                )
+            )
+            endlocal
+
+            """).format(filename=filename, vars=" ".join(self._values.keys()))
+        capture = textwrap.dedent("""\
+            @echo off
+            {deactivate}
+            echo Configuring environment variables
+            """).format(deactivate=deactivate if generate_deactivate else "")
+        result = [capture]
+        for k, v in self._values.items():
+            value = v.value("%{}%".format(k))
+            result.append('set {}={}'.format(k, value))
+
+        content = "\n".join(result)
+        save(filename, content)
+
+    def save_sh(self, filename):
+        capture = textwrap.dedent("""\
+            echo Capturing current environment in deactivate_{filename}
+            echo echo Restoring variables >> deactivate_{filename}
+            for v in {vars}
+            do
+                value=${{!v}}
+                if [ -n "$value" ]
+                then
+                    echo export "$v=$value" >> deactivate_{filename}
+                else
+                    echo unset $v >> deactivate_{filename}
+                fi
+            done
+            echo Configuring environment variables
+            """.format(filename=filename, vars=" ".join(self._values.keys())))
+        result = [capture]
+        for k, v in self._values.items():
+            value = v.value("${}".format(k))
+            if value:
+                result.append('export {}="{}"'.format(k, value))
+            else:
+                result.append('unset {}'.format(k))
+
+        content = "\n".join(result)
+        save(filename, content)
+
+    def compose(self, other):
+        """
+        :type other: Environment
+        """
+        result = Environment()
+        result._values = OrderedDict([(k, v.copy()) for k, v in self._values.items()])
+        for k, v in other._values.items():
+            v = v.copy()
+            existing = result._values.get(k)
+            if existing is None:
+                result._values[k] = v
+            else:
+                existing.update(v)
+        return result
diff --git a/conan/tools/gnu/__init__.py b/conan/tools/gnu/__init__.py
index 32860e46378..73fe02bb986 100644
--- a/conan/tools/gnu/__init__.py
+++ b/conan/tools/gnu/__init__.py
@@ -1 +1,4 @@
 from .make import MakeToolchain
+from conan.tools.gnu.autotoolstoolchain import AutotoolsToolchain
+from conan.tools.gnu.autotoolsdeps import AutotoolsDeps
+from conan.tools.gnu.autotools import Autotools
diff --git a/conan/tools/gnu/autotools.py b/conan/tools/gnu/autotools.py
new file mode 100644
index 00000000000..495658790eb
--- /dev/null
+++ b/conan/tools/gnu/autotools.py
@@ -0,0 +1,232 @@
+import copy
+import os
+import platform
+
+
+from conans.client import tools
+from conans.client.tools.oss import OSInfo,  cross_building, \
+    detected_architecture, detected_os, get_gnu_triplet, get_target_os_arch, get_build_os_arch
+from conans.errors import ConanException
+from conans.model.build_info import DEFAULT_BIN, DEFAULT_INCLUDE, DEFAULT_LIB, DEFAULT_SHARE
+from conans.util.files import get_abs_path
+
+
+class Autotools(object):
+
+    def __init__(self, conanfile, win_bash=False, include_rpath_flags=False):
+        """
+        FIXME: include_rpath_flags CONAN 2.0 to default True? Could break many packages in center
+        """
+        self._conanfile = conanfile
+        self._win_bash = win_bash
+        self._include_rpath_flags = include_rpath_flags
+        self.subsystem = OSInfo().detect_windows_subsystem() if self._win_bash else None
+        self._os = conanfile.settings.get_safe("os")
+        self._os_version = conanfile.settings.get_safe("os.version")
+        self._os_sdk = conanfile.settings.get_safe("os.sdk")
+        self._os_subsystem = conanfile.settings.get_safe("os.subsystem")
+        self._arch = conanfile.settings.get_safe("arch")
+        self._os_target, self._arch_target = get_target_os_arch(conanfile)
+        self._build_type = conanfile.settings.get_safe("build_type")
+        self._compiler = conanfile.settings.get_safe("compiler")
+        self._compiler_version = conanfile.settings.get_safe("compiler.version")
+
+        # Precalculate build, host, target triplets
+        self.build, self.host, self.target = self._get_host_build_target_flags()
+
+    def _get_host_build_target_flags(self):
+        """Based on google search for build/host triplets, it could need a lot
+        and complex verification"""
+
+        if self._os_target and self._arch_target:
+            try:
+                target = get_gnu_triplet(self._os_target, self._arch_target, self._compiler)
+            except ConanException as exc:
+                self._conanfile.output.warn(str(exc))
+                target = None
+        else:
+            target = None
+
+        if hasattr(self._conanfile, 'settings_build'):
+            os_build, arch_build = get_build_os_arch(self._conanfile)
+        else:
+            # FIXME: Why not use 'os_build' and 'arch_build' from conanfile.settings?
+            os_build = detected_os() or platform.system()
+            arch_build = detected_architecture() or platform.machine()
+
+        if os_build is None or arch_build is None or self._arch is None or self._os is None:
+            return False, False, target
+
+        if not cross_building(self._conanfile, os_build, arch_build):
+            return False, False, target
+
+        try:
+            build = get_gnu_triplet(os_build, arch_build, self._compiler)
+        except ConanException as exc:
+            self._conanfile.output.warn(str(exc))
+            build = None
+        try:
+            host = get_gnu_triplet(self._os, self._arch, self._compiler)
+        except ConanException as exc:
+            self._conanfile.output.warn(str(exc))
+            host = None
+        return build, host, target
+
+    def configure(self, configure_dir=None, args=None, build=None, host=None, target=None,
+                  pkg_config_paths=None, vars=None, use_default_install_dirs=True):
+        """
+        http://jingfenghanmax.blogspot.com.es/2010/09/configure-with-host-target-and-build.html
+        https://gcc.gnu.org/onlinedocs/gccint/Configure-Terms.html
+        :param use_default_install_dirs: Use or not the defaulted installation dirs
+
+        """
+        if not self._conanfile.should_configure:
+            return
+        if configure_dir:
+            configure_dir = configure_dir.rstrip("/")
+        else:
+            configure_dir = "."
+        """
+        triplet_args = []
+
+        if build is not False:  # Skipped by user
+            if build or self.build:  # User specified value or automatic
+                triplet_args.append("--build=%s" % (build or self.build))
+
+        if host is not False:   # Skipped by user
+            if host or self.host:  # User specified value or automatic
+                triplet_args.append("--host=%s" % (host or self.host))
+
+        if target is not False:  # Skipped by user
+            if target or self.target:  # User specified value or automatic
+                triplet_args.append("--target=%s" % (target or self.target))
+
+        if pkg_config_paths:
+            pkg_env = {"PKG_CONFIG_PATH":
+                       [os.pathsep.join(get_abs_path(f, self._conanfile.install_folder)
+                                        for f in pkg_config_paths)]}
+        else:
+            # If we are using pkg_config generator automate the pcs location, otherwise it could
+            # read wrong files
+            pkg_env = {"PKG_CONFIG_PATH": [self._conanfile.install_folder]} \
+                if "pkg_config" in self._conanfile.generators else None
+        """
+        configure_dir = configure_dir
+
+        """if self._conanfile.package_folder is not None:
+            if not args:
+                args = ["--prefix=%s" % self._conanfile.package_folder.replace("\\", "/")]
+            elif not self._is_flag_in_args("prefix", args):
+                args.append("--prefix=%s" % self._conanfile.package_folder.replace("\\", "/"))
+
+            all_flags = ["bindir", "sbindir", "libexecdir", "libdir", "includedir", "oldincludedir",
+                         "datarootdir"]
+            help_output = self._configure_help_output(configure_dir)
+            available_flags = [flag for flag in all_flags if "--%s" % flag in help_output]
+
+            if use_default_install_dirs:
+                for varname in ["bindir", "sbindir", "libexecdir"]:
+                    if self._valid_configure_flag(varname, args, available_flags):
+                        args.append("--%s=${prefix}/%s" % (varname, DEFAULT_BIN))
+                if self._valid_configure_flag("libdir", args, available_flags):
+                    args.append("--libdir=${prefix}/%s" % DEFAULT_LIB)
+                for varname in ["includedir", "oldincludedir"]:
+                    if self._valid_configure_flag(varname, args, available_flags):
+                        args.append("--%s=${prefix}/%s" % (varname, DEFAULT_INCLUDE))
+                if self._valid_configure_flag("datarootdir", args, available_flags):
+                    args.append("--datarootdir=${prefix}/%s" % DEFAULT_SHARE)
+
+        with environment_append(pkg_env):
+            with environment_append(vars or self.vars):
+                command = '%s/configure %s %s' % (configure_dir, args_to_string(args),
+                                                  " ".join(triplet_args))
+                self._conanfile.output.info("Calling:\n > %s" % command)
+                self._conanfile.run(command, win_bash=self._win_bash, subsystem=self.subsystem)"""
+
+        cmd = "bash -c 'source autotoolsdeps.sh && source autotools.sh && %s/configure'" % configure_dir
+        self._conanfile.output.info("Calling:\n > %s" % cmd)
+        self._conanfile.run(cmd, win_bash=self._win_bash, subsystem=self.subsystem)
+
+    def _configure_help_output(self, configure_path):
+        from six import StringIO  # Python 2 and 3 compatible
+        mybuf = StringIO()
+        try:
+            self._conanfile.run("%s/configure --help" % configure_path, win_bash=self._win_bash,
+                                output=mybuf)
+        except ConanException as e:
+            self._conanfile.output.warn("Error running `configure --help`: %s" % e)
+            return ""
+        return mybuf.getvalue()
+
+    @staticmethod
+    def _valid_configure_flag(varname, args, available_flags):
+        return not AutoToolsBuildEnvironment._is_flag_in_args(varname, args) and \
+               varname in available_flags
+
+    @staticmethod
+    def _is_flag_in_args(varname, args):
+        flag = "--%s=" % varname
+        return any([flag in arg for arg in args])
+
+    def make(self, target=None):
+        """if not self._build_type:
+            raise ConanException("build_type setting should be defined.")
+        with environment_append(vars or self.vars):
+            str_args = args_to_string(args)
+            cpu_count_option = (("-j%s" % cpu_count(output=self._conanfile.output))
+                                if ("-j" not in str_args and "nmake" not in make_program.lower())
+                                else None)
+            self._conanfile.run("%s" % join_arguments([make_program, target, str_args,
+                                                       cpu_count_option]),
+                                win_bash=self._win_bash, subsystem=self.subsystem)"""
+
+        make_program = self._conanfile.conf["tools.gnu"].make_program
+        if make_program is None:
+            make_program = "mingw32-make" if platform.system() == "Windows" else "make"
+        if platform.system() == "Windows":
+            cmd = "autotoolsdeps.bat && autotools.bat && {}".format(make_program)
+        else:
+            cmd = "bash -c 'source autotoolsdeps.sh "\
+                  "&& source autotools.sh && {}'".format(make_program)
+        self._conanfile.run(cmd, win_bash=self._win_bash, subsystem=self.subsystem)
+
+    def install(self, args=""):
+        if not self._conanfile.should_install:
+            return
+        self.make(target="install")
+
+    def _get_vars(self):
+        def append(*args):
+            ret = []
+            for arg in args:
+                if arg:
+                    if isinstance(arg, list):
+                        ret.extend(arg)
+                    else:
+                        ret.append(arg)
+            return ret
+
+        tmp_compilation_flags = copy.copy(self.flags)
+
+        if tools.is_apple_os(self._os):
+            concat = " ".join(tmp_compilation_flags)
+            if os.environ.get("CFLAGS", None):
+                concat += " " + os.environ.get("CFLAGS", None)
+            if os.environ.get("CXXFLAGS", None):
+                concat += " " + os.environ.get("CXXFLAGS", None)
+            if self._os_version and "-version-min" not in concat and "-target" not in concat:
+                tmp_compilation_flags.append(tools.apple_deployment_target_flag(self._os,
+                                                                                self._os_version,
+                                                                                self._os_sdk,
+                                                                                self._os_subsystem,
+                                                                                self._arch))
+            if "-isysroot" not in concat and platform.system() == "Darwin":
+                tmp_compilation_flags.extend(["-isysroot",
+                                              tools.XCRun(self._conanfile.settings).sdk_path])
+            if "-arch" not in concat and self._arch:
+                tmp_compilation_flags.extend(["-arch", tools.to_apple_arch(self._arch)])
+
+        cxx_flags = append(tmp_compilation_flags, self.cxx_flags, self.cppstd_flag)
+        c_flags = tmp_compilation_flags
+
+        return ld_flags, cpp_flags, libs, cxx_flags, c_flags
diff --git a/conan/tools/gnu/autotoolsdeps.py b/conan/tools/gnu/autotoolsdeps.py
new file mode 100644
index 00000000000..0b66fa584e6
--- /dev/null
+++ b/conan/tools/gnu/autotoolsdeps.py
@@ -0,0 +1,62 @@
+from conan.tools.env import Environment
+
+
+class AutotoolsDeps(object):
+    def __init__(self, conanfile):
+        # Set the generic objects before mapping to env vars to let the user
+        # alter some value
+        self._conanfile = conanfile
+        deps_cpp_info = conanfile.deps_cpp_info
+        self.libs = list(deps_cpp_info.libs)
+        self.libs.extend(list(deps_cpp_info.system_libs))
+        self.include_paths = list(deps_cpp_info.include_paths)
+        self.library_paths = list(deps_cpp_info.lib_paths)
+        self.defines = list(deps_cpp_info.defines)
+        self.cflags = list(deps_cpp_info.cflags)
+        self.cxx_flags = list(deps_cpp_info.cxxflags)
+        self.sharedlinkflags = list(deps_cpp_info.sharedlinkflags)
+        self.exelinkflags = list(deps_cpp_info.exelinkflags)
+        self.frameworks = list(deps_cpp_info.frameworks)
+        self.frameworks_paths = list(deps_cpp_info.framework_paths)
+        self.sysroot = deps_cpp_info.sysroot
+
+    def generate(self):
+        # cpp_flags
+        cpp_flags = []
+        include_paths = ['-I"%s"' % p for p in self.include_paths]
+        cpp_flags.extend(include_paths)
+        cpp_flags.extend(["-D%s" % define for define in self.defines])
+
+        # Libs
+        libs = ["-l%s" % library for library in self.libs]
+
+        # Ldflags
+        # TODO: Discuss, should the helper filter frameworks based on compiler?
+        frameworks = ["-framework %s" % framework for framework in self.frameworks]
+        frameworks_paths = ["-F %s" % framework_path for framework_path in self.frameworks_paths]
+        ldflags = self.sharedlinkflags
+        ldflags.extend(self.exelinkflags)
+        ldflags.extend(frameworks)
+        ldflags.extend(frameworks_paths)
+        lib_paths = ['-L"%s"' % p for p in self.library_paths]
+        ldflags.extend(lib_paths)
+
+        # cflags
+        cflags = []
+        cxxflags = []
+
+        if self.sysroot:
+            srf = '--sysroot={}'.format(self.sysroot)
+            cflags.append(srf)
+            cxxflags.append(srf)
+            ldflags.append(srf)
+
+        env = Environment()
+        env["CPPFLAGS"].append(cpp_flags)
+        env["LIBS"].append(libs)
+        env["LDFLAGS"].append(ldflags)
+        env["CXXFLAGS"].append(cxxflags)
+        env["CFLAGS"].append(cflags)
+
+        env.save_sh("autotoolsdeps.sh")
+        env.save_bat("autotoolsdeps.bat")
diff --git a/conan/tools/gnu/autotoolstoolchain.py b/conan/tools/gnu/autotoolstoolchain.py
new file mode 100644
index 00000000000..60d9847a0d5
--- /dev/null
+++ b/conan/tools/gnu/autotoolstoolchain.py
@@ -0,0 +1,111 @@
+from conan.tools._compilers import architecture_flag, build_type_flags
+from conan.tools.env import Environment
+# FIXME: need to refactor this import and bring to conan.tools
+from conans.client.build.cppstd_flags import cppstd_flag_new
+
+
+class AutotoolsToolchain(object):
+    def __init__(self, conanfile):
+        self._conanfile = conanfile
+        build_type = self._conanfile.settings.get_safe("build_type")
+
+        # TODO: compiler.runtime for Visual studio?
+        # defines
+        self.ndebug = None
+        if build_type in ['Release', 'RelWithDebInfo', 'MinSizeRel']:
+            self.ndebug = "NDEBUG"
+        self.gcc_cxx11_abi = self._cxx11_abi_define()
+        self.defines = []
+
+        # cxxflags, cflags
+        self.cxxflags = []
+        self.cflags = []
+        self.ldflags = []
+        self.libcxx = self._libcxx()
+        self.fpic = self._conanfile.options.get_safe("fPIC")
+
+        # FIXME: This needs to be imported here into conan.tools
+        self.cppstd = cppstd_flag_new(self._conanfile.settings)
+        self.arch_flag = architecture_flag(self._conanfile.settings)
+        # TODO: This is also covering compilers like Visual Studio, necessary to test it (&remove?)
+        self.build_type_flags = build_type_flags(self._conanfile.settings)
+
+    def _rpaths_link(self):
+        # TODO: Not used yet
+        lib_paths = self._conanfile.deps_cpp_info.lib_paths
+        compiler = _base_compiler(settings)
+        if compiler in GCC_LIKE:
+            return ['-Wl,-rpath,"%s"' % (x.replace("\\", "/"))
+                    for x in lib_paths if x]
+
+    def _cxx11_abi_define(self):
+        # https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html
+        # The default is libstdc++11, only specify the contrary '_GLIBCXX_USE_CXX11_ABI=0'
+        settings = self._conanfile.settings
+        libcxx = settings.get_safe("compiler.libcxx")
+        if not libcxx:
+            return
+
+        compiler = settings.get_safe("compiler.base") or settings.get_safe("compiler")
+        if compiler == "gcc":
+            if libcxx == 'libstdc++':
+                return '_GLIBCXX_USE_CXX11_ABI=0'
+
+    def _libcxx(self):
+        settings = self._conanfile.settings
+        libcxx = settings.get_safe("compiler.libcxx")
+        if not libcxx:
+            return
+
+        compiler = settings.get_safe("compiler.base") or settings.get_safe("compiler")
+
+        if compiler in ['clang', 'apple-clang']:
+            if libcxx in ['libstdc++', 'libstdc++11']:
+                return '-stdlib=libstdc++'
+            elif libcxx == 'libc++':
+                return '-stdlib=libc++'
+        elif compiler == 'sun-cc':
+            return ({"libCstd": "-library=Cstd",
+                     "libstdcxx": "-library=stdcxx4",
+                     "libstlport": "-library=stlport4",
+                     "libstdc++": "-library=stdcpp"}.get(libcxx))
+        elif compiler == "qcc":
+            return "-Y _%s" % str(libcxx)
+
+    def _environment(self):
+        env = Environment()
+        # defines
+        if self.ndebug:
+            self.defines.append(self.ndebug)
+        if self.gcc_cxx11_abi:
+            self.defines.append(self.gcc_cxx11_abi)
+
+        if self.libcxx:
+            self.cxxflags.append(self.libcxx)
+
+        if self.cppstd:
+            self.cxxflags.append(self.cppstd)
+
+        if self.arch_flag:
+            self.cxxflags.append(self.arch_flag)
+            self.cflags.append(self.arch_flag)
+            self.ldflags.append(self.arch_flag)
+
+        if self.build_type_flags:
+            self.cxxflags.extend(self.build_type_flags)
+            self.cflags.extend(self.build_type_flags)
+
+        if self.fpic:
+            self.cxxflags.append("-fPIC")
+            self.cflags.append("-fPIC")
+
+        env["CPPFLAGS"].append(["-D{}".format(d) for d in self.defines])
+        env["CXXFLAGS"].append(self.cxxflags)
+        env["CFLAGS"].append(self.cflags)
+        env["LDFLAGS"].append(self.ldflags)
+        return env
+
+    def generate(self):
+        env = self._environment()
+        env.save_sh("autotools.sh")
+        env.save_bat("autotools.bat")
diff --git a/conans/client/envvars/environment.py b/conans/client/envvars/environment.py
index 84f68f906a9..3665ae469b4 100644
--- a/conans/client/envvars/environment.py
+++ b/conans/client/envvars/environment.py
@@ -174,7 +174,7 @@ def _files(env_vars, vars_with_spaces, flavor, activate_tpl, deactivate_tpl, ven
     activate_content = activate_tpl.render(environment_file=env_filepath,
                                            modified_vars=modified_vars, new_vars=new_vars,
                                            venv_name=venv_name)
-    deactivate_content = deactivate_tpl.render(modified_vars=modified_vars, new_vars=new_vars, 
+    deactivate_content = deactivate_tpl.render(modified_vars=modified_vars, new_vars=new_vars,
                                                venv_name=venv_name)
 
     environment_lines = ["{}={}".format(name, value) for name, value, _ in ret]
diff --git a/conans/client/generators/__init__.py b/conans/client/generators/__init__.py
index 3f0c96a6281..573d7afff27 100644
--- a/conans/client/generators/__init__.py
+++ b/conans/client/generators/__init__.py
@@ -66,7 +66,8 @@ def __init__(self):
                             "deploy": DeployGenerator,
                             "markdown": MarkdownGenerator}
         self._new_generators = ["CMakeToolchain", "CMakeDeps", "MakeToolchain", "MSBuildToolchain",
-                                "MesonToolchain", "MSBuildDeps", "QbsToolchain", "msbuild"]
+                                "MesonToolchain", "MSBuildDeps", "QbsToolchain", "msbuild",
+                                "AutotoolsDeps", "AutotoolsToolchain"]
 
     def add(self, name, generator_class, custom=False):
         if name not in self._generators or custom:
@@ -95,6 +96,12 @@ def _new_generator(self, generator_name, output):
         elif generator_name == "MakeToolchain":
             from conan.tools.gnu import MakeToolchain
             return MakeToolchain
+        elif generator_name == "AutotoolsDeps":
+            from conan.tools.gnu import AutotoolsDeps
+            return AutotoolsDeps
+        elif generator_name == "AutotoolsToolchain":
+            from conan.tools.gnu import AutotoolsToolchain
+            return AutotoolsToolchain
         elif generator_name == "MSBuildToolchain":
             from conan.tools.microsoft import MSBuildToolchain
             return MSBuildToolchain
diff --git a/conans/test/assets/sources.py b/conans/test/assets/sources.py
index bd9934897b1..052e96b904f 100644
--- a/conans/test/assets/sources.py
+++ b/conans/test/assets/sources.py
@@ -35,6 +35,11 @@
     std::cout << "  {{ msg or name }} __x86_64__ defined\n";
     #endif
 
+    // Libstdc++
+    #if defined _GLIBCXX_USE_CXX11_ABI
+    std::cout << "  {{ msg or name }} _GLIBCXX_USE_CXX11_ABI "<< _GLIBCXX_USE_CXX11_ABI << "\n";
+    #endif
+
     // COMPILER VERSIONS
     #if _MSC_VER
     std::cout << "  {{ msg or name }} _MSC_VER" << _MSC_VER<< "\n";
diff --git a/conans/test/functional/toolchains/gnu/__init__.py b/conans/test/functional/toolchains/gnu/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/conans/test/functional/toolchains/gnu/test_autotools.py b/conans/test/functional/toolchains/gnu/test_autotools.py
new file mode 100644
index 00000000000..189a29f4a34
--- /dev/null
+++ b/conans/test/functional/toolchains/gnu/test_autotools.py
@@ -0,0 +1,206 @@
+import os
+import platform
+import textwrap
+import time
+
+import pytest
+
+from conans.test.assets.autotools import gen_makefile_am, gen_configure_ac
+from conans.test.assets.sources import gen_function_cpp
+from conans.test.functional.utils import check_exe_run
+from conans.test.utils.tools import TestClient
+from conans.util.files import touch
+
+
+@pytest.mark.skipif(platform.system() != "Linux", reason="Requires Autotools")
+@pytest.mark.tool_autotools()
+def test_autotools():
+    client = TestClient(path_with_spaces=False)
+    client.run("new hello/0.1 --template=v2_cmake")
+    client.run("create .")
+
+    main = gen_function_cpp(name="main", includes=["hello"], calls=["hello"])
+    makefile_am = gen_makefile_am(main="main", main_srcs="main.cpp")
+    configure_ac = gen_configure_ac()
+
+    conanfile = textwrap.dedent("""
+        from conans import ConanFile
+        from conan.tools.gnu import AutotoolsToolchain, Autotools, AutotoolsDeps
+
+        class TestConan(ConanFile):
+            requires = "hello/0.1"
+            settings = "os", "compiler", "arch", "build_type"
+            exports_sources = "configure.ac", "Makefile.am", "main.cpp"
+
+            def generate(self):
+                deps = AutotoolsDeps(self)
+                deps.generate()
+                tc = AutotoolsToolchain(self)
+                tc.generate()
+
+            def build(self):
+                self.run("aclocal")
+                self.run("autoconf")
+                self.run("automake --add-missing --foreign")
+                autotools = Autotools(self)
+                autotools.configure()
+                autotools.make()
+                autotools.install()
+        """)
+
+    client.save({"conanfile.py": conanfile,
+                 "configure.ac": configure_ac,
+                 "Makefile.am": makefile_am,
+                 "main.cpp": main}, clean_first=True)
+    client.run("install .")
+    client.run("build .")
+    client.run_command("./main")
+    check_exe_run(client.out, "main", "gcc", None, "Release", "x86_64", None, cxx11_abi=0)
+    assert "hello/0.1: Hello World Release!" in client.out
+
+
+def build_windows_subsystem(profile, make_program):
+    """ The AutotoolsDeps can be used also in pure Makefiles, if the makefiles follow
+    the Autotools conventions
+    """
+    # FIXME: cygwin in CI (my local machine works) seems broken for path with spaces
+    client = TestClient(path_with_spaces=False)
+    client.run("new hello/0.1 --template=v2_cmake")
+    # TODO: Test Windows subsystems in CMake, at least msys is broken
+    os.rename(os.path.join(client.current_folder, "test_package"),
+              os.path.join(client.current_folder, "test_package2"))
+    client.save({"profile": profile})
+    client.run("create . --profile=profile")
+    print(client.out)
+
+    main = gen_function_cpp(name="main", includes=["hello"], calls=["hello"])
+    makefile = textwrap.dedent("""\
+        app: main.o
+        	$(CXX) $(CFLAGS) $(LDFLAGS) -o app main.o $(LIBS)
+
+        main.o: main.cpp
+        	$(CXX) $(CFLAGS) $(CXXFLAGS) $(CPPFLAGS) -c -o main.o main.cpp
+        """)
+
+    conanfile = textwrap.dedent("""
+        from conans import ConanFile
+        from conan.tools.gnu import AutotoolsToolchain, Autotools, AutotoolsDeps
+
+        class TestConan(ConanFile):
+            requires = "hello/0.1"
+            settings = "os", "compiler", "arch", "build_type"
+            exports_sources = "Makefile"
+
+            def generate(self):
+                deps = AutotoolsDeps(self)
+                deps.generate()
+                tc = AutotoolsToolchain(self)
+                tc.generate()
+
+            def build(self):
+                autotools = Autotools(self)
+                autotools.make()
+        """)
+    client.save({"main.cpp": main,
+                 "Makefile": makefile,
+                 "conanfile.py": conanfile,
+                 "profile": profile}, clean_first=True)
+
+    client.run("install . --profile=profile")
+    client.run_command("autotoolsdeps.bat && autotools.bat && {}".format(make_program))
+    print(client.out)
+    client.run_command("app")
+    # TODO: fill compiler version when ready
+    check_exe_run(client.out, "main", "gcc", None, "Release", "x86_64", None)
+    assert "hello/0.1: Hello World Release!" in client.out
+
+    client.save({"main.cpp": gen_function_cpp(name="main", msg="main2",
+                                              includes=["hello"], calls=["hello"])})
+    # Make sure it is newer
+    t = time.time() + 1
+    touch(os.path.join(client.current_folder, "main.cpp"), (t, t))
+
+    client.run("build .")
+    print(client.out)
+    client.run_command("app")
+    # TODO: fill compiler version when ready
+    check_exe_run(client.out, "main2", "gcc", None, "Release", "x86_64", None, cxx11_abi=0)
+    assert "hello/0.1: Hello World Release!" in client.out
+    return client.out
+
+
+@pytest.mark.tool_cygwin
+@pytest.mark.skipif(platform.system() != "Windows", reason="Needs windows")
+def test_autotoolsdeps_cygwin():
+    gcc = textwrap.dedent("""
+        [settings]
+        os=Windows
+        os.subsystem=cygwin
+        compiler=gcc
+        compiler.version=4.9
+        compiler.libcxx=libstdc++
+        arch=x86_64
+        build_type=Release
+        """)
+    out = build_windows_subsystem(gcc, make_program="make")
+    print(out)
+    assert "__MSYS__" not in out
+    assert "MINGW" not in out
+    assert "main2 __CYGWIN__1" in out
+
+
+@pytest.mark.tool_mingw
+@pytest.mark.skipif(platform.system() != "Windows", reason="Needs windows")
+def test_autotoolsdeps_mingw():
+    gcc = textwrap.dedent("""
+        [settings]
+        os=Windows
+        compiler=gcc
+        compiler.version=4.9
+        compiler.libcxx=libstdc++
+        arch=x86_64
+        build_type=Release
+        """)
+    out = build_windows_subsystem(gcc, make_program="mingw32-make")
+    print(out)
+    assert "__MSYS__" not in out
+    assert "main2 __MINGW64__1" in out
+
+
+@pytest.mark.tool_mingw64
+@pytest.mark.skipif(platform.system() != "Windows", reason="Needs windows")
+def test_autotoolsdeps_mingw_msys():
+    gcc = textwrap.dedent("""
+        [settings]
+        os=Windows
+        compiler=gcc
+        compiler.version=4.9
+        compiler.libcxx=libstdc++
+        arch=x86_64
+        build_type=Release
+        """)
+    out = build_windows_subsystem(gcc, make_program="mingw32-make")
+    print(out)
+    assert "__MSYS__" not in out
+    assert "main2 __MINGW64__1" in out
+
+
+@pytest.mark.tool_msys2
+@pytest.mark.skipif(platform.system() != "Windows", reason="Needs windows")
+def test_autotoolsdeps_msys():
+    gcc = textwrap.dedent("""
+        [settings]
+        os=Windows
+        os.subsystem=msys2
+        compiler=gcc
+        compiler.version=4.9
+        compiler.libcxx=libstdc++
+        arch=x86_64
+        build_type=Release
+        """)
+    out = build_windows_subsystem(gcc, make_program="make")
+    print(out)
+    # Msys2 is a rewrite of Msys, using Cygwin
+    assert "MINGW" not in out
+    assert "main2 __MSYS__1" in out
+    assert "main2 __CYGWIN__1" in out
diff --git a/conans/test/functional/toolchains/test_make.py b/conans/test/functional/toolchains/gnu/test_make.py
similarity index 100%
rename from conans/test/functional/toolchains/test_make.py
rename to conans/test/functional/toolchains/gnu/test_make.py
diff --git a/conans/test/functional/utils.py b/conans/test/functional/utils.py
index 29fcc98f8b1..a9180eb8403 100644
--- a/conans/test/functional/utils.py
+++ b/conans/test/functional/utils.py
@@ -23,7 +23,8 @@ def check_vs_runtime(exe, client, vs_version, build_type, static, architecture="
             raise NotImplementedError()
 
 
-def check_exe_run(output, names, compiler, version, build_type, arch, cppstd, definitions=None):
+def check_exe_run(output, names, compiler, version, build_type, arch, cppstd, definitions=None,
+                  cxx11_abi=None):
     output = str(output)
     names = names if isinstance(names, list) else [names]
 
@@ -41,6 +42,8 @@ def check_exe_run(output, names, compiler, version, build_type, arch, cppstd, de
             assert "{} _MSVC_LANG20{}".format(name, cppstd) in output
 
         elif compiler == "gcc":
+            assert "{} __GNUC__".format(name) in output
+
             if arch == "x86":
                 assert "{} __i386__ defined".format(name) in output
             elif arch == "x86_64":
@@ -59,6 +62,9 @@ def check_exe_run(output, names, compiler, version, build_type, arch, cppstd, de
                                 "17": "201703"}[cppstd]
                 assert "{} __cplusplus{}".format(name, cppstd_value) in output
 
+            if cxx11_abi is not None:
+                assert "{} _GLIBCXX_USE_CXX11_ABI {}".format(name, cxx11_abi) in output
+
         if definitions:
             for k, v in definitions.items():
                 assert "{}: {}".format(k, v) in output
diff --git a/conans/test/unittests/tools/env/__init__.py b/conans/test/unittests/tools/env/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/conans/test/unittests/tools/env/test_env.py b/conans/test/unittests/tools/env/test_env.py
new file mode 100644
index 00000000000..74c150385b9
--- /dev/null
+++ b/conans/test/unittests/tools/env/test_env.py
@@ -0,0 +1,125 @@
+import platform
+import subprocess
+import textwrap
+
+import pytest
+
+from conan.tools.env import Environment
+from conans.client.tools import chdir
+from conans.test.utils.test_files import temp_folder
+from conans.util.files import save
+
+
+def test_compose():
+    env = Environment()
+    env["MyVar"].define("MyValue")
+    env["MyVar2"].define("MyValue2")
+    env["MyVar3"].define("MyValue3")
+    env["MyVar4"].define("MyValue4")
+
+    env2 = Environment()
+    env2["MyVar"].define("MyNewValue")
+    env2["MyVar2"].append("MyNewValue2")
+    env2["MyVar3"].prepend("MyNewValue3")
+    env2["MyVar4"].clean()
+
+    env3 = env.compose(env2)
+    assert env3["MyVar"].value("MyVar") == "MyNewValue"
+    assert env3["MyVar2"].value("MyVar2") == 'MyValue2 MyNewValue2'
+    assert env3["MyVar3"].value("MyVar3") == 'MyNewValue3 MyValue3'
+
+
+def test_env_files():
+    env = Environment()
+    env["MyVar"].define("MyValue")
+    env["MyVar1"].define("MyValue1")
+    env["MyVar2"].append("MyValue2")
+    env["MyVar3"].prepend("MyValue3")
+    env["MyVar4"].clean()
+    env["MyVar5"].define("MyValue5 With Space5=More Space5;:More")
+    folder = temp_folder()
+
+    prevenv = {"MyVar1": "OldVar1",
+               "MyVar2": "OldVar2",
+               "MyVar3": "OldVar3",
+               "MyVar4": "OldVar4"}
+
+    display_bat = textwrap.dedent("""\
+        @echo off
+        echo MyVar=%MyVar%!!
+        echo MyVar1=%MyVar1%!!
+        echo MyVar2=%MyVar2%!!
+        echo MyVar3=%MyVar3%!!
+        echo MyVar4=%MyVar4%!!
+        echo MyVar5=%MyVar5%!!
+        """)
+
+    display_sh = textwrap.dedent("""\
+        echo MyVar=$MyVar!!
+        echo MyVar1=$MyVar1!!
+        echo MyVar2=$MyVar2!!
+        echo MyVar3=$MyVar3!!
+        echo MyVar4=$MyVar4!!
+        echo MyVar5=$MyVar5!!
+        """)
+
+    with chdir(folder):
+        if platform.system() == "Windows":
+            env.save_bat("test.bat")
+            save("display.bat", display_bat)
+            cmd = "test.bat && display.bat && deactivate_test.bat && display.bat"
+
+            exe = None
+        else:
+            env.save_sh("test.sh")
+            save("display.sh", display_sh)
+            cmd = ". test.sh && . display.sh && . deactivate_test.sh && . display.sh"
+
+            exe = "/bin/bash"
+        out, _ = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+                                  env=prevenv, shell=True, executable=exe).communicate()
+
+    out = out.decode()
+    assert "MyVar=MyValue!!" in out
+    assert "MyVar1=MyValue1!!" in out
+    assert "MyVar2=OldVar2 MyValue2!!" in out
+    assert "MyVar3=MyValue3 OldVar3!!" in out
+    assert "MyVar4=!!" in out
+    assert "MyVar5=MyValue5 With Space5=More Space5;:More!!" in out
+
+    assert "MyVar=!!" in out
+    assert "MyVar1=OldVar1!!" in out
+    assert "MyVar2=OldVar2!!" in out
+    assert "MyVar3=OldVar3!!" in out
+    assert "MyVar4=OldVar4!!" in out
+    assert "MyVar5=!!" in out
+
+
+@pytest.mark.skipif(platform.system() != "Windows", reason="Requires MSBuild")
+def test_windows_case_insensitive():
+    # Append and define operation over the same variable in Windows preserve order
+    env = Environment()
+    env["MyVar"].define("MyValueA")
+    env["MYVAR"].define("MyValueB")
+    env["MyVar1"].define("MyValue1A")
+    env["MYVAR1"].append("MyValue1B")
+    folder = temp_folder()
+
+    display_bat = textwrap.dedent("""\
+        @echo off
+        echo MyVar=%MyVar%!!
+        echo MyVar1=%MyVar1%!!
+        """)
+
+    with chdir(folder):
+        env.save_bat("test.bat")
+        save("display.bat", display_bat)
+        cmd = "test.bat && display.bat && deactivate_test.bat && display.bat"
+        out, _ = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+                                  shell=True).communicate()
+
+    out = out.decode()
+    assert "MyVar=MyValueB!!" in out
+    assert "MyVar=!!" in out
+    assert "MyVar1=MyValue1A MyValue1B!!" in out
+    assert "MyVar1=!!" in out


Code review comments:
diff_hunk: @@ -0,0 +1,206 @@
+import os
+import platform
+import textwrap
+import time
+
+import pytest
+
+from conans.test.assets.autotools import gen_makefile_am, gen_configure_ac
+from conans.test.assets.sources import gen_function_cpp
+from conans.test.functional.utils import check_exe_run
+from conans.test.utils.tools import TestClient
+from conans.util.files import touch
+
+
+@pytest.mark.skipif(platform.system() != "Linux", reason="Requires Autotools")
+@pytest.mark.tool_autotools()
+def test_autotools():
+    client = TestClient(path_with_spaces=False)
+    client.run("new hello/0.1 --template=v2_cmake")
+    client.run("create .")
+
+    main = gen_function_cpp(name="main", includes=["hello"], calls=["hello"])
+    makefile_am = gen_makefile_am(main="main", main_srcs="main.cpp")
+    configure_ac = gen_configure_ac()
+
+    conanfile = textwrap.dedent("""
+        from conans import ConanFile
+        from conan.tools.gnu import AutotoolsToolchain, Autotools, AutotoolsDeps
+
+        class TestConan(ConanFile):
+            requires = "hello/0.1"
+            settings = "os", "compiler", "arch", "build_type"
+            exports_sources = "configure.ac", "Makefile.am", "main.cpp"
+
+            def generate(self):
+                deps = AutotoolsDeps(self)
+                deps.generate()
+                tc = AutotoolsToolchain(self)
+                tc.generate()
+
+            def build(self):
+                self.run("aclocal")
+                self.run("autoconf")
+                self.run("automake --add-missing --foreign")
+                autotools = Autotools(self)
+                autotools.configure()
+                autotools.make()
+                autotools.install()
+        """)
+
+    client.save({"conanfile.py": conanfile,
+                 "configure.ac": configure_ac,
+                 "Makefile.am": makefile_am,
+                 "main.cpp": main}, clean_first=True)
+    client.run("install .")
+    client.run("build .")
+    client.run_command("./main")
+    check_exe_run(client.out, "main", "gcc", None, "Release", "x86_64", None, cxx11_abi=0)
+    assert "hello/0.1: Hello World Release!" in client.out
+
+
+def build_windows_subsystem(profile, make_program):
+    """ The AutotoolsDeps can be used also in pure Makefiles, if the makefiles follow
+    the Autotools conventions
+    """
+    # FIXME: cygwin in CI (my local machine works) seems broken for path with spaces
+    client = TestClient(path_with_spaces=False)
+    client.run("new hello/0.1 --template=v2_cmake")
+    # TODO: Test Windows subsystems in CMake, at least msys is broken
+    os.rename(os.path.join(client.current_folder, "test_package"),
+              os.path.join(client.current_folder, "test_package2"))
+    client.save({"profile": profile})
+    client.run("create . --profile=profile")
+    print(client.out)
+
+    main = gen_function_cpp(name="main", includes=["hello"], calls=["hello"])
+    makefile = textwrap.dedent("""\
+        app: main.o
+        	$(CXX) $(CFLAGS) $(LDFLAGS) -o app main.o $(LIBS)
+
+        main.o: main.cpp
+        	$(CXX) $(CFLAGS) $(CXXFLAGS) $(CPPFLAGS) -c -o main.o main.cpp
+        """)
+
+    conanfile = textwrap.dedent("""
+        from conans import ConanFile
+        from conan.tools.gnu import AutotoolsToolchain, Autotools, AutotoolsDeps
+
+        class TestConan(ConanFile):
+            requires = "hello/0.1"
+            settings = "os", "compiler", "arch", "build_type"
+            exports_sources = "Makefile"
+
+            def generate(self):
+                deps = AutotoolsDeps(self)
+                deps.generate()
+                tc = AutotoolsToolchain(self)
+                tc.generate()
+
+            def build(self):
+                autotools = Autotools(self)
+                autotools.make()
+        """)
+    client.save({"main.cpp": main,
+                 "Makefile": makefile,
+                 "conanfile.py": conanfile,
+                 "profile": profile}, clean_first=True)
+
+    client.run("install . --profile=profile")
+    client.run_command("autotoolsdeps.bat && autotools.bat && {}".format(make_program))
+    print(client.out)
+    client.run_command("app")
+    # TODO: fill compiler version when ready
+    check_exe_run(client.out, "main", "gcc", None, "Release", "x86_64", None)
+    assert "hello/0.1: Hello World Release!" in client.out
+
+    client.save({"main.cpp": gen_function_cpp(name="main", msg="main2",
+                                              includes=["hello"], calls=["hello"])})
+    # Make sure it is newer
+    t = time.time() + 1
+    touch(os.path.join(client.current_folder, "main.cpp"), (t, t))
+
+    client.run("build .")
+    print(client.out)
+    client.run_command("app")
+    # TODO: fill compiler version when ready
+    check_exe_run(client.out, "main2", "gcc", None, "Release", "x86_64", None, cxx11_abi=0)
+    assert "hello/0.1: Hello World Release!" in client.out
+    return client.out
+
+
+@pytest.mark.tool_cygwin
+@pytest.mark.skipif(platform.system() != "Windows", reason="Needs windows")
+def test_autotoolsdeps_cygwin():
+    gcc = textwrap.dedent("""
+        [settings]
+        os=Windows
+        os.subsystem=cygwin
+        compiler=gcc
+        compiler.version=4.9
+        compiler.libcxx=libstdc++
+        arch=x86_64
+        build_type=Release
+        """)
+    out = build_windows_subsystem(gcc, make_program="make")
+    print(out)
+    assert "__MSYS__" not in out
+    assert "MINGW" not in out
+    assert "main2 __CYGWIN__1" in out
+
+
+@pytest.mark.tool_mingw
path: conans/test/functional/toolchains/gnu/test_autotools.py
text: @user2:
```suggestion
@user1.mark.tool_msys2
@user1.mark.tool_mingw32
```
not sure about an old `tool_mingw`, let's use the one from the MSYS2

@author:
It is another mingw, quite popular that doesn't work under the umbrella of Msys2. We need to make sure it is working too, the commands, paths, syntax, might be different of what Msys2 is doing, so lets keep this variant too.