Repository: conan-io/conan

Issue:
[question] What's the intended way to work with makefiles without autotools in 2.0
### What is your question?

I'm new to Conan with 2.0 and my team is currently using custom makefiles without autotools to build our projects. Googling around I end up on 1.x docs of Conan where there does seem to be built in generators for makefiles. So how do I work with makefiles in 2.0?

Packaging at least seems easy. I can just not have a `build()` function and use a wrapper script to build with make first and then call `conan create`. Maybe I could have the `build()` function call the python `subprocess` library to handle the build itself though?

However consuming conan packages is not as straight forward. Should I just be using the `VirtualBuildEnv` and relying on env vars in my makefiles?

Are there any good example projects that build with makefiles and conan 2.0?

### Have you read the CONTRIBUTING guide?

- [X] I've read the CONTRIBUTING guide


Discussion:
Hi @daltonv 

Not sure if you are aware of the ``conan new autotools_lib -d name=pkg -d version=0.1``.
It is true that it is an autotools project, not a pure Makefile one, but I think stripping out the autotools part should be relatively easy and the overall approach should be a good start.

> I can just not have a build() function and use a wrapper script to build with make first and then call conan create. 

Sorry, I don't know what you mean. Typically running ``self.run("make ...")`` should be the way to go. In general running ``subprocess`` yourself is not recommended, but using the ``self.run()``

Yes, the ``test_package/conanfile.py`` inside the template would be almost a typical consumer project. Not ``VirtualBuildEnv``, but depending on what you are doing ``AutotoolsDeps`` or ``PkgConfigDeps`` should probably be the way to go. Even if it is called ``AutotoolsDeps`` it is mostly generating environment scripts with the environment variables necessary to locate the dependencies, nothing really strictly of "autotools" only. Probably same for ``AutotoolsToolchain``.


I'd recommend giving a try to the template, removing the autotools bits, and if you have any issues, let us know and we could try to put together an example. Thanks!

Actually, this might be useful for some other users, maybe we want to make a ``conan new makefile_lib`` (and _exe) templates too.
Ah I didn't see the `ConanFile` class has a `self.run()` method. That makes it easy.

I'll try out the autotools project and report back.

I definitely think that a makefile example would be helpful. I'm working on embedded projects, and while I can control my build system, many embedded sdks and libraries still use plain makefiles to build. 
> Ah I didn't see the ConanFile class has a self.run() method. That makes it easy.

Yes, and beyond easier, the ``self.run()`` might do other stuff, like automatically inject environment variables scripts.

> I'll try out the autotools project and report back.

I definitely think that a makefile example would be helpful. I'm working on embedded projects, and while I can control my build system, many embedded sdks and libraries still use plain makefiles to build.

Perfect, looking forward your feedback, please share you findings and we will propose a new pure Makefiles template. Thanks!

Okay it was actually pretty easy to get the makefiles running now that I know where to start! I used the `AutotoolsDeps` generator as suggested.

A couple points of feedback:

## Docs

Initially I thought I just missed something in the docs about the `self.run()` method, and I did, but I feel like it could have been more obvious. It's indirectly talked about in the [build() tutorial](https://docs.conan.io/2/tutorial/creating_packages/build_packages.html#changes-introduced-in-the-recipe) but I missed that on my first glance. The only other obvious place I saw it was in reference material for [build()](https://docs.conan.io/2/reference/conanfile/methods/build.html). I think the docs should at least have a reference section about the `run()` command as currently you have to look at the source code to understand the args. Additionally a makfile example would probably make it more obvious that the `run()` command exists.

## [tool_requires] flags

Currently the `AutotoolsDeps` generator does not include dependencies from [tool_requires] settings. The [docs](https://docs.conan.io/2/reference/tools/gnu/autotoolsdeps.html#generated-files) seem to imply that this will be included in the future. I really hope so, because I want to package the arm gcc compiler to be used in all my projects, and there are some arm specific cflags I want all projects to use. So it would be nice if the compiler package provides those to downstream projects.

I can work around this by just adding those settings directly in the makefile, but it's much cleaner to cross compiler for x86 and various arm archs in `CMake` land as the [tool_requires] `cpp_info` settings are carried over. 

Even if I put the cflags in my profile file `AutotoolsDeps` doesn't pick them up. Example:
```ini
[conf]
tools.build:cflags+=["-gstrict-dwarf", "-march=armv6-m"]
```

Overall though it's pretty easy to setup makefile projects with `AutotoolsDeps` + `self.run(make)`. I think an example that shows this would be very nice. 

Just need to generate with `AutotoolsDeps` and call `self.run(make)` the `package()` function will likely be custom for each makefile project. 

Within the files created by `conan new autotools_lib` there is a folder called `test_package` which I believe is an example of a project that "consumes" the library built. However, it is still autotools... I mean `autotools` is required if I want the consumer test_package to build without running `conan build ...` right?

The same is true with other generators (e.g., cmake), `conan install .` on the consumer project will create the files that then my consumer's `CMakeLists.txt` can include them in order to pull in the library helper vars needed to provide the CPPFLAGS/LDFLAGS/etc for building. With those generators, I can then run `cmake` followed by  `make` directly to build my consumer project (i.e., I don't have to run `conan build`).

I don't see how the same thing can be done with the `autotools_lib` template and a standard Makefile project. I don't see any files that are generated by `Autotoolsdeps` generator that a `Makefile` could include to build the consumer project where I simply run "make" and the consumer project builds.  Am I missing something? Or maybe I'm misunderstanding the workflow?
Hey @gabeblack. The generated `conanbuild.sh` script and friends from `AutotoolsDeps` can inject info into the makefile. This is because makefiles will consume environment variables.

So the correct flow will be:
1. Run `conan install .`
2. Run `source conanbuild.sh` - This loads the shell scripts that set env vars for stuff like `CC`, `CFLAGS`, etc.
3. Run `make` - Your makefile should be using the special env vars. Like `$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@`.

If you add `self. run(make)` to your `conanfile.py` (and correctly generated the `AutotoolsDeps`) the `run()` method automagically loads the env vars for you, so you skip step 2, and `conan build .` handles it for you. 
I've had a very similar experience to the OP and landing on this issue was the help I needed since I was getting the impression that since it was in the 1.x docs but not the 2.x that this kind of support had been removed.

I started from the Autotools template and removed things from there, although I still use `Autotools.make()` and `Autotools.install()` instead of `self.run()` as all they do is call the correct things anyway. Having mention of Autotools at all does make the contents of my `conanfile.py` a bit misleading, but I am also using `generators = "AutotoolsToolchain", "AutotoolsDeps"` to get the environment set up. Maybe some sort of alias or explicit support would be good.

Anyway, the upshot is that I can retro-fit old `make` based projects to use Conan 2.0 successfully with this approach. Just posting this to echo the OP's thoughts, emphasis the use-case, and reassure anyone else coming across this issue.
Hello folks,

I am currently looking into this issue myself. One problem with the `AutotoolsDeps` generator (and many others) is that it only aggregates the variables of multiple packages. For example, in the generator output, `CPPFLAGS` contains all the `-Idirectory` flags for the various packages, concatenated together. Which, to be sure, is fine for many use cases. But if you need the `CPPFLAGS` variable specifically for the `foobaz` package, then you're out of luck---there is no `CONAN_INCLUDE_DIRS_FOOBAZ` variable that can be referenced. You'll have to do a `conan install` for that package alone.

Also, it's not clear to me if/how the `AutotoolsDeps` generator addresses some of the other variables provided by the `make` one, like `CONAN_RES_DIRS_FOOBAZ`. As far as I can tell, it only deals with the official GNU-recognized environment variables.

Is there any work in progress on a Make-compatible generator that will provide per-package variables? Would a PR to port the old `make` generator to 2.0 (or perhaps develop a new one) be OK in principle?
@iskunk -- I actually ran in to the same thing. So yes, you can make the `Autotools`/`AutotoolsDeps` work for many makefile packages, but it is incomplete for the reason you gave. 

I ended up having to sort of write my own generator to do what you and I were needing. The following will create a `CONAN_<pkg_name>_PKG_FOLDER` which from that you can tag on "include", "libs", etc to get the include, libs, etc folders. Or you can extend this implementation to make the package-specific variables. I also had it rename the global CPPFLAGS to be CONAN_CPPFLAGS, LDFLAGS to CONAN_LDFLAGS, etc, etc. In my use-case, I didn't want the conan CPPFLAGS to be used for my entire project. Just parts. So I renamed the global variables conan creates and prefixed them with 'CONAN_'. Finally, this generator creates a file that can be included with make. So 'include conanbuild.make' will work and you don't need to source environment prior to running the build.

TBH, it would be nice if they just brought back the makefile generator support. If people aren't against it, maybe a variation of what I have done here could be added to conan. 

```
import re
from conan import ConanFile
from conan.tools.files import copy, save
from conan.tools.gnu import AutotoolsToolchain, AutotoolsDeps


class MakefileDeps:
    # conan 2.0 deprecated the makefile generator... so implementing a simple makefile generator
    def __init__(self, conanfile):
        self._conanfile = conanfile

    def generate(self):
        autotools = AutotoolsDeps(self._conanfile)
        env_vars = autotools.environment.vars(self._conanfile)
        autotools.generate()

        content = []

        # Rename the global CPPFLAGS, LDFLAGS, etc., with a "CONAN_" prefix --  CONAN_CPPFLAGS, CONAN_LDFLAGS, ...
        for key, value in env_vars.items("${name}"):
            value = re.sub(r"\$(\w+)", r"$(\1)", value)
            value = value.replace('"', '\\"')
            content.append(re.sub(f"{key}", f"CONAN_{key}", f"{key} := {value}"))

        # add CONAN_<PKG_NAME>_PKG_FOLDER := <path/to/package/folder> variables
        for require, dep in self._conanfile.dependencies.items():
            package_name = re.sub("-", "_", dep.ref.name).upper()
            if dep.package_folder:
                content.append(f"CONAN_{package_name}_PKG_FOLDER := {dep.package_folder}")
        save(self._conanfile, f"{self._conanfile.generators_folder}/conanbuild.make", "\n".join(content))

```
@gabeblack: While one can get a lot of what's needed from the top-level package directories, you'd still want access to the `cpp_info` variables for more complex dependencies. Especially packages that make use of the [components](https://docs.conan.io/2/reference/conanfile/methods/package_info.html#components) abstraction.

All the same, thanks for sharing your code. I'm still finding my way around the codebase, and this gave me some helpful clues. I hope to have something to show here before too long.
Hello , 
Is there any updates on the issue ? 
I also need the make generator for my project and I do not want to use any workaround solutions that might break.

Resolving patch:
diff --git a/conan/tools/gnu/__init__.py b/conan/tools/gnu/__init__.py
--- a/conan/tools/gnu/__init__.py
+++ b/conan/tools/gnu/__init__.py
@@ -3,3 +3,4 @@
 from conan.tools.gnu.autotoolsdeps import AutotoolsDeps
 from conan.tools.gnu.pkgconfig import PkgConfig
 from conan.tools.gnu.pkgconfigdeps import PkgConfigDeps
+from conan.tools.gnu.makedeps import MakeDeps
diff --git a/conan/tools/gnu/makedeps.py b/conan/tools/gnu/makedeps.py
new file mode 100644
--- /dev/null
+++ b/conan/tools/gnu/makedeps.py
@@ -0,0 +1,634 @@
+"""Makefile generator for Conan dependencies
+
+This generator creates a Makefile (conandeps.mk) with variables for each dependency and consider their components.
+To simplify its usage, it also creates global variables with aggregated values from all dependencies.
+This generator does not work like a toolchain, it does not include settings.
+
+For better customization, it allows appending prefixes as flags variables:
+
+- CONAN_LIB_FLAG: Add a prefix to all libs variables, e.g. -l
+- CONAN_DEFINE_FLAG: Add a prefix to all defines variables, e.g. -D
+- CONAN_SYSTEM_LIB_FLAG: Add a prefix to all system_libs variables, e.g. -l
+- CONAN_INCLUDE_DIR_FLAG: Add a prefix to all include dirs variables, e.g. -I
+- CONAN_LIB_DIR_FLAG: Add a prefix to all lib dirs variables, e.g. -L
+- CONAN_BIN_DIR_FLAG: Add a prefix to all bin dirs variables, e.g. -L
+
+
+The conandeps.mk file layout is as follows:
+
+- CONAN_DEPS: list all transitive and direct dependencies names without version (e.g. zlib)
+- Iterate over each dependency and its components:
+    - Prints name, version, root folder, regular folders, libs and flags
+    - Components are rootified to avoid repeating same prefix twice for the root folder
+    - Components libs, folder and flags are solved as variables to avoid repeating same name twice
+- Aggregated global variables for simplification, sum all dependencies to common variables (e.g. CONAN_INCLUDE_DIRS)
+
+"""
+
+import os
+import re
+import textwrap
+
+from jinja2 import Template, StrictUndefined
+
+from conan.internal import check_duplicated_generator
+from conan.tools.files import save
+
+
+CONAN_MAKEFILE_FILENAME = "conandeps.mk"
+
+
+def _get_formatted_dirs(folders: list, prefix_path_: str, name: str) -> list:
+    """ Format the directories to be used in the makefile, adding the prefix path if needed
+    :param folders: list of directories
+    :param prefix_path_: prefix path
+    :param name: component name
+    :return: list of formatted directories
+    """
+    ret = []
+    for directory in folders:
+        directory = os.path.normpath(directory).replace("\\", "/")
+        prefix = ""
+        if not os.path.isabs(directory):
+            prefix = f"$(CONAN_ROOT_{name})/"
+        elif directory.startswith(prefix_path_):
+            prefix = f"$(CONAN_ROOT_{name})/"
+            directory = os.path.relpath(directory, prefix_path_).replace("\\", "/")
+        ret.append(f"{prefix}{directory}")
+    return ret
+
+
+def _makefy(name: str) -> str:
+    """
+    Convert a name to Make-variable-friendly syntax
+    :param name: The name to be converted
+    :return: Safe makefile variable, not including bad characters that are not parsed correctly
+    """
+    return re.sub(r'[^0-9A-Z_]', '_', name.upper())
+
+
+def _conan_prefix_flag(variable: str) -> str:
+    """
+    Return a global flag to be used as prefix to any value in the makefile
+    """
+    return f"$(CONAN_{variable.upper()}_FLAG)" if variable else ""
+
+
+def _common_cppinfo_variables() -> dict:
+    """
+    Regular cppinfo variables exported by any Conanfile and their Makefile prefixes
+    """
+    return {
+        "objects": None,
+        "libs": "lib",
+        "defines": "define",
+        "cflags": None,
+        "cxxflags": None,
+        "sharedlinkflags": None,
+        "exelinkflags": None,
+        "frameworks": None,
+        "requires": None,
+        "system_libs": "system_lib",
+    }
+
+
+def _common_cppinfo_dirs() -> dict:
+    """
+    Regular cppinfo folders exported by any Conanfile and their Makefile prefixes
+    """
+    return {
+        "includedirs": "include_dir",
+        "libdirs": "lib_dir",
+        "bindirs": "bin_dir",
+        "srcdirs": None,
+        "builddirs": None,
+        "resdirs": None,
+        "frameworkdirs": None,
+    }
+
+
+def _jinja_format_list_values() -> str:
+    """
+    Template method to format a list of values in a Makefile,
+    - Empty variables are not exposed in the Makefile
+    - Single value variables are exposed in a single line
+    - Multiple value variables are exposed in multiple lines with a tabulation
+    e.g.
+    define_variable_value("FOO", ["single_value"])
+    output:
+    FOO = single_value
+
+    define_variable_value("BAR", ["value1", "value2"])
+    output:
+    BAR = \
+        value1 \
+        value2
+    """
+    return textwrap.dedent("""\
+            {%- macro define_variable_value_safe(var, object, attribute) -%}
+            {%- if attribute in object -%}
+            {{ define_variable_value("{}".format(var), object[attribute]) }}
+            {%- endif -%}
+            {%- endmacro %}
+
+            {%- macro define_variable_value(var, values) -%}
+            {%- if values is not none -%}
+            {%- if values|length > 0 -%}
+            {{ var }} = {{ format_list_values(values) }}
+            {%- endif -%}
+            {%- endif -%}
+            {%- endmacro %}
+
+            {%- macro format_list_values(values) -%}
+            {% if values|length == 1 %}
+            {{ values[0] }}
+
+            {% elif values|length > 1 %}
+            \\
+            {% for value in values[:-1] %}
+            \t{{ value }} \\
+            {% endfor %}
+            \t{{ values|last }}
+
+            {% endif %}
+            {%- endmacro %}
+            """)
+
+
+class MakeInfo:
+    """
+    Store temporary information about each dependency
+    """
+
+    def __init__(self, name: str, dirs: list, flags: list):
+        """
+        :param name: Dependency or component raw name
+        :param dirs: cpp_info folders supported by the dependency
+        :param flags: cpp_info variables supported by the dependency
+        """
+        self._name = name
+        self._dirs = dirs
+        self._flags = flags
+
+    @property
+    def name(self) -> str:
+        return self._name
+
+    @property
+    def dirs(self) -> list:
+        """
+        :return: List of cpp_info folders supported by the dependency without duplicates
+        """
+        return list(set(self._dirs))
+
+    @property
+    def flags(self) -> list:
+        """
+        :return: List of cpp_info variables supported by the dependency without duplicates
+        """
+        return list(set(self._flags))
+
+    def dirs_append(self, directory: str):
+        """
+        Add a new cpp_info folder to the dependency
+        """
+        self._dirs.append(directory)
+
+    def flags_append(self, flag: str):
+        """
+        Add a new cpp_info variable to the dependency
+        """
+        self._flags.append(flag)
+
+
+class GlobalContentGenerator:
+    """
+    Generates the formatted content for global variables (e.g. CONAN_DEPS, CONAN_LIBS)
+    """
+
+    template = textwrap.dedent("""\
+
+            # Aggregated global variables
+
+
+            {{ define_variable_value("CONAN_INCLUDE_DIRS", deps_cpp_info_dirs.include_dirs) -}}
+            {{- define_variable_value("CONAN_LIB_DIRS", deps_cpp_info_dirs.lib_dirs) -}}
+            {{- define_variable_value("CONAN_BIN_DIRS", deps_cpp_info_dirs.bin_dirs) -}}
+            {{- define_variable_value("CONAN_SRC_DIRS", deps_cpp_info_dirs.src_dirs) -}}
+            {{- define_variable_value("CONAN_BUILD_DIRS", deps_cpp_info_dirs.build_dirs) -}}
+            {{- define_variable_value("CONAN_RES_DIRS", deps_cpp_info_dirs.res_dirs) -}}
+            {{- define_variable_value("CONAN_FRAMEWORK_DIRS", deps_cpp_info_dirs.framework_dirs) -}}
+            {{- define_variable_value("CONAN_OBJECTS", deps_cpp_info_flags.objects) -}}
+            {{- define_variable_value("CONAN_LIBS", deps_cpp_info_flags.libs) -}}
+            {{- define_variable_value("CONAN_DEFINES", deps_cpp_info_flags.defines) -}}
+            {{- define_variable_value("CONAN_CFLAGS", deps_cpp_info_flags.cflags) -}}
+            {{- define_variable_value("CONAN_CXXFLAGS", deps_cpp_info_flags.cxxflags) -}}
+            {{- define_variable_value("CONAN_SHAREDLINKFLAGS", deps_cpp_info_flags.sharedlinkflags) -}}
+            {{- define_variable_value("CONAN_EXELINKFLAGS", deps_cpp_info_flags.exelinkflags) -}}
+            {{- define_variable_value("CONAN_FRAMEWORKS", deps_cpp_info_flags.frameworks) -}}
+            {{- define_variable_value("CONAN_REQUIRES", deps_cpp_info_flags.requires) -}}
+            {{- define_variable_value("CONAN_SYSTEM_LIBS", deps_cpp_info_flags.system_libs) -}}
+            """)
+
+    template_deps = textwrap.dedent("""\
+
+            {{ define_variable_value("CONAN_DEPS", deps) }}
+            """)
+
+    def content(self, deps_cpp_info_dirs: dict, deps_cpp_info_flags: dict) -> str:
+        """
+        Generate content for Cppinfo variables (e.g. CONAN_LIBS, CONAN_INCLUDE_DIRS)
+        :param deps_cpp_info_dirs: Formatted dependencies folders
+        :param deps_cpp_info_flags: Formatted dependencies variables
+        """
+        context = {"deps_cpp_info_dirs": deps_cpp_info_dirs,
+                   "deps_cpp_info_flags": deps_cpp_info_flags}
+        template = Template(_jinja_format_list_values() + self.template, trim_blocks=True, lstrip_blocks=True, undefined=StrictUndefined)
+        return template.render(context)
+
+    def deps_content(self, dependencies_names: list) -> str:
+        """
+        Generate content for CONAN_DEPS (e.g. CONAN_DEPS = zlib, openssl)
+        :param dependencies_names: Non-formatted dependencies names
+        """
+        context = {"deps": dependencies_names}
+        template = Template(_jinja_format_list_values() + self.template_deps, trim_blocks=True, lstrip_blocks=True, undefined=StrictUndefined)
+        return template.render(context)
+
+
+class GlobalGenerator:
+    """
+    Process all collected dependencies and parse to generate global content
+    """
+
+    def __init__(self, conanfile, make_infos):
+        self._conanfile = conanfile
+        self._make_infos = make_infos
+
+    def _get_dependency_dirs(self) -> dict:
+        """
+        List regular directories from cpp_info and format them to be used in the makefile
+        """
+        dirs = {}
+        for var in _common_cppinfo_dirs():
+            key = var.replace("dirs", "_dirs")
+            dirs[key] = [f"$(CONAN_{key.upper()}_{_makefy(makeinfo.name)})" for makeinfo in self._make_infos if var in makeinfo.dirs]
+        return dirs
+
+    def _get_dependency_flags(self) -> dict:
+        """
+        List common variables from cpp_info and format them to be used in the makefile
+        """
+        flags = {}
+        for var in _common_cppinfo_variables():
+            key = var.replace("dirs", "_dirs")
+            flags[key] = [f"$(CONAN_{key.upper()}_{_makefy(makeinfo.name)})" for makeinfo in self._make_infos if var in makeinfo.flags]
+        return flags
+
+    def generate(self) -> str:
+        """
+        Process folder and variables for a dependency and generates its Makefile content
+        """
+        glob_content_gen = GlobalContentGenerator()
+        dirs = self._get_dependency_dirs()
+        flags = self._get_dependency_flags()
+        return glob_content_gen.content(dirs, flags)
+
+    def deps_generate(self) -> str:
+        """
+        Process dependencies names and generates its Makefile content.
+        It should be added as first variable in the Makefile.
+        """
+        dependencies = [makeinfo.name for makeinfo in self._make_infos if makeinfo.name != self._conanfile.name]
+        glob_content_gen = GlobalContentGenerator()
+        return glob_content_gen.deps_content(dependencies)
+
+
+class DepComponentContentGenerator:
+    """
+    Generates Makefile content for each dependency component
+    """
+
+    template = textwrap.dedent("""\
+        # {{ dep.ref.name }}::{{ comp_name }}
+
+        {{  define_variable_value_safe("CONAN_INCLUDE_DIRS_{}_{}".format(dep_name, name), cpp_info_dirs, 'include_dirs') -}}
+        {{- define_variable_value_safe("CONAN_LIB_DIRS_{}_{}".format(dep_name, name), cpp_info_dirs, 'lib_dirs') -}}
+        {{- define_variable_value_safe("CONAN_BIN_DIRS_{}_{}".format(dep_name, name), cpp_info_dirs, 'bin_dirs') -}}
+        {{- define_variable_value_safe("CONAN_SRC_DIRS_{}_{}".format(dep_name, name), cpp_info_dirs, 'src_dirs') -}}
+        {{- define_variable_value_safe("CONAN_BUILD_DIRS_{}_{}".format(dep_name, name), cpp_info_dirs, 'build_dirs') -}}
+        {{- define_variable_value_safe("CONAN_RES_DIRS_{}_{}".format(dep_name, name), cpp_info_dirs, 'res_dirs') -}}
+        {{- define_variable_value_safe("CONAN_FRAMEWORK_DIRS_{}_{}".format(dep_name, name), cpp_info_dirs, 'framework_dirs') -}}
+        {{- define_variable_value_safe("CONAN_OBJECTS_{}_{}".format(dep_name, name), cpp_info_flags, 'objects') -}}
+        {{- define_variable_value_safe("CONAN_LIBS_{}_{}".format(dep_name, name), cpp_info_flags, 'libs') -}}
+        {{- define_variable_value_safe("CONAN_DEFINES_{}_{}".format(dep_name, name), cpp_info_flags, 'defines') -}}
+        {{- define_variable_value_safe("CONAN_CFLAGS_{}_{}".format(dep_name, name), cpp_info_flags, 'cflags') -}}
+        {{- define_variable_value_safe("CONAN_CXXFLAGS_{}_{}".format(dep_name, name), cpp_info_flags, 'cxxflags') -}}
+        {{- define_variable_value_safe("CONAN_SHAREDLINKFLAGS_{}_{}".format(dep_name, name), cpp_info_flags, 'sharedlinkflags') -}}
+        {{- define_variable_value_safe("CONAN_EXELINKFLAGS_{}_{}".format(dep_name, name), cpp_info_flags, 'exelinkflags') -}}
+        {{- define_variable_value_safe("CONAN_FRAMEWORKS_{}_{}".format(dep_name, name), cpp_info_flags, 'frameworks') -}}
+        {{- define_variable_value_safe("CONAN_REQUIRES_{}_{}".format(dep_name, name), cpp_info_flags, 'requires') -}}
+        {{- define_variable_value_safe("CONAN_SYSTEM_LIBS_{}_{}".format(dep_name, name), cpp_info_flags, 'system_libs') -}}
+        """)
+
+    def __init__(self, dependency: object, component_name: str, dirs: dict, flags: dict):
+        """
+        :param dependency: The dependency object that owns the component
+        :param component_name: component raw name e.g. poco::poco_json
+        :param dirs: The component cpp_info folders
+        :param flags: The component cpp_info variables
+        """
+        self._dep = dependency
+        self._name = component_name
+        self._dirs = dirs or {}
+        self._flags = flags or {}
+
+    def content(self) -> str:
+        """
+        Format template and generate Makefile component
+        """
+        context = {
+            "dep": self._dep,
+            "comp_name": self._name,
+            "dep_name": _makefy(self._dep.ref.name),
+            "name": _makefy(self._name),
+            "cpp_info_dirs": self._dirs,
+            "cpp_info_flags": self._flags
+        }
+        template = Template(_jinja_format_list_values() + self.template, trim_blocks=True, lstrip_blocks=True, undefined=StrictUndefined)
+        return template.render(context)
+
+
+class DepContentGenerator:
+    """
+    Generates Makefile content for a dependency
+    """
+
+    template = textwrap.dedent("""\
+
+        # {{ dep.ref }}
+
+        CONAN_NAME_{{ name }} = {{ dep.ref.name }}
+
+        CONAN_VERSION_{{ name }} = {{ dep.ref.version }}
+
+        CONAN_REFERENCE_{{ name }} = {{ dep.ref }}
+
+        CONAN_ROOT_{{ name }} = {{ root }}
+
+        {{  define_variable_value("CONAN_SYSROOT_{}".format(name), sysroot) -}}
+        {{- define_variable_value_safe("CONAN_INCLUDE_DIRS_{}".format(name), cpp_info_dirs, 'include_dirs') -}}
+        {{- define_variable_value_safe("CONAN_LIB_DIRS_{}".format(name), cpp_info_dirs, 'lib_dirs') -}}
+        {{- define_variable_value_safe("CONAN_BIN_DIRS_{}".format(name), cpp_info_dirs, 'bin_dirs') -}}
+        {{- define_variable_value_safe("CONAN_SRC_DIRS_{}".format(name), cpp_info_dirs, 'src_dirs') -}}
+        {{- define_variable_value_safe("CONAN_BUILD_DIRS_{}".format(name), cpp_info_dirs, 'build_dirs') -}}
+        {{- define_variable_value_safe("CONAN_RES_DIRS_{}".format(name), cpp_info_dirs, 'res_dirs') -}}
+        {{- define_variable_value_safe("CONAN_FRAMEWORK_DIRS_{}".format(name), cpp_info_dirs, 'framework_dirs') -}}
+        {{- define_variable_value_safe("CONAN_OBJECTS_{}".format(name), cpp_info_flags, 'objects') -}}
+        {{- define_variable_value_safe("CONAN_LIBS_{}".format(name), cpp_info_flags, 'libs') -}}
+        {{- define_variable_value_safe("CONAN_DEFINES_{}".format(name), cpp_info_flags, 'defines') -}}
+        {{- define_variable_value_safe("CONAN_CFLAGS_{}".format(name), cpp_info_flags, 'cflags') -}}
+        {{- define_variable_value_safe("CONAN_CXXFLAGS_{}".format(name), cpp_info_flags, 'cxxflags') -}}
+        {{- define_variable_value_safe("CONAN_SHAREDLINKFLAGS_{}".format(name), cpp_info_flags, 'sharedlinkflags') -}}
+        {{- define_variable_value_safe("CONAN_EXELINKFLAGS_{}".format(name), cpp_info_flags, 'exelinkflags') -}}
+        {{- define_variable_value_safe("CONAN_FRAMEWORKS_{}".format(name), cpp_info_flags, 'frameworks') -}}
+        {{- define_variable_value_safe("CONAN_REQUIRES_{}".format(name), cpp_info_flags, 'requires') -}}
+        {{- define_variable_value_safe("CONAN_SYSTEM_LIBS_{}".format(name), cpp_info_flags, 'system_libs') -}}
+        {{- define_variable_value("CONAN_COMPONENTS_{}".format(name), components) -}}
+        """)
+
+    def __init__(self, dependency: object, root: str, sysroot: str, dirs: dict, flags: dict):
+        self._dep = dependency
+        self._root = root
+        self._sysroot = sysroot
+        self._dirs = dirs or {}
+        self._flags = flags or {}
+
+    def content(self) -> str:
+        """
+        Parse dependency variables and generate its Makefile content
+        """
+        context = {
+            "dep": self._dep,
+            "name": _makefy(self._dep.ref.name),
+            "root": self._root,
+            "sysroot": self._sysroot,
+            "components": list(self._dep.cpp_info.get_sorted_components().keys()),
+            "cpp_info_dirs": self._dirs,
+            "cpp_info_flags": self._flags,
+        }
+        template = Template(_jinja_format_list_values() + self.template, trim_blocks=True, lstrip_blocks=True, undefined=StrictUndefined)
+        return template.render(context)
+
+
+class DepComponentGenerator:
+    """
+    Generates Makefile content for a dependency component
+    """
+
+    def __init__(self, dependency: object, makeinfo: MakeInfo, component_name: str, component: object, root: str):
+        """
+        :param dependency: The dependency object that owns the component
+        :param makeinfo: Makeinfo to store component variables
+        :param component_name: The component raw name e.g. poco::poco_json
+        :param component: The component object to obtain cpp_info variables
+        :param root: The dependency root folder
+        """
+        self._dep = dependency
+        self._name = component_name
+        self._comp = component
+        self._root = root
+        self._makeinfo = makeinfo
+
+    def _get_component_dirs(self) -> dict:
+        """
+        List regular directories from cpp_info and format them to be used in the makefile
+        :return: A dictionary with regular folder name and its formatted path
+        """
+        dirs = {}
+        for var, flag in _common_cppinfo_dirs().items():
+            cppinfo_value = getattr(self._comp, var)
+            formatted_dirs = _get_formatted_dirs(cppinfo_value, self._root, _makefy(self._name))
+            if formatted_dirs:
+                self._makeinfo.dirs_append(var)
+                var = var.replace("dirs", "_dirs")
+                formatted_dirs = self._rootify(self._root, self._dep.ref.name, cppinfo_value)
+                dirs[var] = [_conan_prefix_flag(flag) + it for it in formatted_dirs]
+        return dirs
+
+    def _rootify(self, root: str, root_id: str, path_list: list) -> list:
+        """
+        Replaces component folder path by its root node folder path in case they match
+        :param root: root folder path for component's father
+        :param root_id: component's dependency name
+        :param path_list: folder list available in the component
+        :return: A formatted folder list, solving root folder path as prefix
+        """
+        root_len = len(root)
+        root_with_sep = root + os.sep
+        root_var_ref = f"$(CONAN_ROOT_{_makefy(root_id)})"
+        return [root_var_ref + path[root_len:].replace("\\", "/") if path.startswith(root_with_sep) else path for path in path_list]
+
+    def _get_component_flags(self) -> dict:
+        """
+        List common variables from cpp_info and format them to be used in the makefile
+        :return: A dictionary with regular flag/variable name and its formatted value with prefix
+        """
+        flags = {}
+        for var, prefix_var in _common_cppinfo_variables().items():
+            cppinfo_value = getattr(self._comp, var)
+            if not cppinfo_value:
+                continue
+            if "flags" in var:
+                cppinfo_value = [var.replace('"', '\\"') for var in cppinfo_value]
+            if cppinfo_value:
+                flags[var] = [_conan_prefix_flag(prefix_var) + it for it in cppinfo_value]
+                self._makeinfo.flags_append(var)
+        return flags
+
+    def generate(self) -> str:
+        """
+        Process component cpp_info variables and generate its Makefile content
+        :return: Component Makefile content
+        """
+        dirs = self._get_component_dirs()
+        flags = self._get_component_flags()
+        comp_content_gen = DepComponentContentGenerator(self._dep, self._name, dirs, flags)
+        comp_content = comp_content_gen.content()
+        return comp_content
+
+
+class DepGenerator:
+    """
+    Process a dependency cpp_info variables and generate its Makefile content
+    """
+
+    def __init__(self, dependency: object):
+        self._dep = dependency
+        self._info = MakeInfo(self._dep.ref.name, [], [])
+
+    @property
+    def makeinfo(self) -> MakeInfo:
+        """
+        :return: Dependency folder and flags
+        """
+        return self._info
+
+    def _get_dependency_dirs(self, root: str, dependency: object) -> dict:
+        """
+        List regular directories from cpp_info and format them to be used in the makefile
+        :param root: Package root folder
+        :param dep: Dependency object
+        :return: A dictionary with regular folder name and its formatted path
+        """
+        dirs = {}
+        for var, prefix in _common_cppinfo_dirs().items():
+            cppinfo_value = getattr(dependency.cpp_info, var)
+            formatted_dirs = _get_formatted_dirs(cppinfo_value, root, _makefy(dependency.ref.name))
+            if formatted_dirs:
+                self._info.dirs_append(var)
+                var = var.replace("dirs", "_dirs")
+                dirs[var] = [_conan_prefix_flag(prefix) + it for it in formatted_dirs]
+        return dirs
+
+    def _get_dependency_flags(self, dependency: object) -> dict:
+        """
+        List common variables from cpp_info and format them to be used in the makefile
+        :param dep: Dependency object
+        """
+        flags = {}
+        for var, prefix_var in _common_cppinfo_variables().items():
+            cppinfo_value = getattr(dependency.cpp_info, var)
+            # Use component cpp_info info when does not provide any value
+            if not cppinfo_value:
+                cppinfo_value = [f"$(CONAN_{var.upper()}_{_makefy(dependency.ref.name)}_{_makefy(name)})" for name, obj in dependency.cpp_info.components.items() if getattr(obj, var.lower())]
+                # avoid repeating same prefix twice
+                prefix_var = ""
+            if "flags" in var:
+                cppinfo_value = [var.replace('"', '\\"') for var in cppinfo_value]
+            if cppinfo_value:
+                self._info.flags_append(var)
+                flags[var] = [_conan_prefix_flag(prefix_var) + it for it in cppinfo_value]
+        return flags
+
+    def _get_sysroot(self, root: str) -> list:
+        """
+        Get the sysroot of the dependency. Sysroot is a list of directories, or a single directory
+        """
+        sysroot = self._dep.cpp_info.sysroot if isinstance(self._dep.cpp_info.sysroot, list) else [self._dep.cpp_info.sysroot]
+        # sysroot may return ['']
+        if not sysroot or not sysroot[0]:
+            return []
+        return _get_formatted_dirs(sysroot, root, _makefy(self._dep.ref.name)) if sysroot and sysroot[0] else None
+
+    def _get_root_folder(self):
+        """
+        Get the root folder of the dependency
+        """
+        root = self._dep.recipe_folder if self._dep.package_folder is None else self._dep.package_folder
+        return root.replace("\\", "/")
+
+    def generate(self) -> str:
+        """
+        Process dependency folders and flags to generate its Makefile content. Plus, execute same
+        steps for each component
+        """
+        root = self._get_root_folder()
+        sysroot = self._get_sysroot(root)
+        dirs = self._get_dependency_dirs(root, self._dep)
+        flags = self._get_dependency_flags(self._dep)
+        dep_content_gen = DepContentGenerator(self._dep, root, sysroot, dirs, flags)
+        content = dep_content_gen.content()
+
+        for comp_name, comp in self._dep.cpp_info.get_sorted_components().items():
+            component_gen = DepComponentGenerator(self._dep, self._info, comp_name, comp, root)
+            content += component_gen.generate()
+
+        return content
+
+
+class MakeDeps:
+    """
+    Generates a Makefile with the variables needed to build a project with the specified.
+    """
+
+    _title = "# This Makefile has been generated by Conan. DO NOT EDIT!\n"
+
+    def __init__(self, conanfile: object):
+        """
+        :param conanfile: ``< ConanFile object >`` The current recipe object. Always use ``self``.
+        """
+        self._conanfile = conanfile
+
+    def generate(self) -> None:
+        """
+        Collects all dependencies and components, then, generating a Makefile
+        """
+        check_duplicated_generator(self, self._conanfile)
+
+        host_req = self._conanfile.dependencies.host
+        build_req = self._conanfile.dependencies.build  # tool_requires
+        test_req = self._conanfile.dependencies.test
+
+        content_buffer = f"{self._title}\n"
+        deps_buffer = ""
+
+        # Filter the build_requires not activated for any requirement
+        dependencies = [tup for tup in list(host_req.items()) + list(build_req.items()) + list(test_req.items()) if not tup[0].build]
+
+        make_infos = []
+
+        for require, dep in dependencies:
+            # Require is not used at the moment, but its information could be used, and will be used in Conan 2.0
+            if require.build:
+                continue
+
+            dep_gen = DepGenerator(dep)
+            make_infos.append(dep_gen.makeinfo)
+            deps_buffer += dep_gen.generate()
+
+        glob_gen = GlobalGenerator(self._conanfile, make_infos)
+        content_buffer += glob_gen.deps_generate() + deps_buffer + glob_gen.generate()
+
+        save(self._conanfile, CONAN_MAKEFILE_FILENAME, content_buffer)
+        self._conanfile.output.info(f"Generated {CONAN_MAKEFILE_FILENAME}")
diff --git a/conans/client/generators/__init__.py b/conans/client/generators/__init__.py
--- a/conans/client/generators/__init__.py
+++ b/conans/client/generators/__init__.py
@@ -21,6 +21,7 @@
                "IntelCC": "conan.tools.intel",
                "XcodeDeps": "conan.tools.apple", "XcodeToolchain": "conan.tools.apple",
                "PremakeDeps": "conan.tools.premake",
+               "MakeDeps": "conan.tools.gnu",
                }
 
 


Test patch:
diff --git a/conans/test/functional/toolchains/gnu/test_makedeps.py b/conans/test/functional/toolchains/gnu/test_makedeps.py
new file mode 100644
--- /dev/null
+++ b/conans/test/functional/toolchains/gnu/test_makedeps.py
@@ -0,0 +1,139 @@
+import textwrap
+
+import platform
+import pytest
+
+from conans.test.assets.autotools import gen_makefile
+from conans.test.assets.sources import gen_function_cpp, gen_function_h
+from conans.test.utils.tools import TestClient
+from conan.tools.gnu.makedeps import CONAN_MAKEFILE_FILENAME
+
+
+@pytest.mark.tool("make" if platform.system() != "Windows" else "msys2")
+def test_make_deps_definitions_escape():
+    """
+    MakeDeps has to escape the definitions properly.
+    """
+    client = TestClient(path_with_spaces=False)
+    conanfile = textwrap.dedent(r'''
+        from conan import ConanFile
+        class HelloLib(ConanFile):
+            def package_info(self):
+                self.cpp_info.defines.append("USER_CONFIG=\"user_config.h\"")
+                self.cpp_info.defines.append('OTHER="other.h"')
+                self.cpp_info.cflags.append("flag1=\"my flag1\"")
+                self.cpp_info.cxxflags.append('flag2="my flag2"')
+        ''')
+    client.save({"conanfile.py": conanfile})
+    client.run("export . --name=hello --version=0.1.0")
+    client.run("install --requires=hello/0.1.0 --build=missing -g MakeDeps")
+    client.run_command(f"make --print-data-base -f {CONAN_MAKEFILE_FILENAME}", assert_error=True)
+    assert r'CONAN_CXXFLAGS_HELLO = flag2=\"my flag2\"' in client.out
+    assert r'CONAN_CFLAGS_HELLO = flag1=\"my flag1\"' in client.out
+    assert r'CONAN_DEFINES_HELLO = $(CONAN_DEFINE_FLAG)USER_CONFIG="user_config.h" $(CONAN_DEFINE_FLAG)OTHER="other.h"' in client.out
+
+
+def test_makedeps_with_tool_requires():
+    """
+    MakeDeps has to create any test requires to be declared on the recipe.
+    """
+    conanfile = textwrap.dedent(r'''
+        from conan import ConanFile
+        class HelloLib(ConanFile):
+            def package_info(self):
+                self.cpp_info.libs = [self.name]
+        ''')
+    client = TestClient(path_with_spaces=False)
+    client.save({"conanfile.py": conanfile})
+    client.run("create . --name=hello --version=0.1.0")
+    client.run("create . --name=test --version=0.1.0")
+    client.run("create . --name=tool --version=0.1.0")
+    # Create library having build and test requires
+    conanfile = textwrap.dedent(r'''
+        from conan import ConanFile
+        class HelloLib(ConanFile):
+            def build_requirements(self):
+                self.test_requires('hello/0.1.0')
+                self.test_requires('test/0.1.0')
+                self.tool_requires('tool/0.1.0')
+        ''')
+    client.save({"conanfile.py": conanfile}, clean_first=True)
+    client.run("install . -g MakeDeps")
+    content = client.load(CONAN_MAKEFILE_FILENAME)
+    assert "CONAN_NAME_TEST" in content
+    assert "CONAN_NAME_HELLO" in content
+    assert "CONAN_NAME_TOOL" not in content
+
+
+@pytest.mark.tool("make" if platform.system() != "Windows" else "msys2")
+def test_makedeps_with_makefile_build():
+    """
+    Build a small application using MakeDeps generator and with components
+    """
+    client = TestClient(path_with_spaces=False)
+    with client.chdir("lib"):
+        client.save({"Makefile": gen_makefile(libs=["hello"]),
+                    "hello.cpp": gen_function_cpp(name="hello", includes=["hello"], calls=["hello"]),
+                    "hello.h": gen_function_h(name="hello"),
+                    "conanfile.py": textwrap.dedent(r'''
+                    from conan import ConanFile
+                    from conan.tools.gnu import Autotools
+                    from conan.tools.files import copy
+                    import os
+                    class PackageConan(ConanFile):
+                        exports_sources = ("Makefile", "hello.cpp", "hello.h")
+                        settings = "os", "arch", "compiler"
+
+                        def configure(self):
+                            self.win_bash = self.settings.os == "Windows"
+
+                        def build(self):
+                            self.run("make -f Makefile")
+
+                        def package(self):
+                            copy(self, "libhello.a", src=self.build_folder, dst=os.path.join(self.package_folder, "lib"))
+                            copy(self, "hello.h", src=self.source_folder, dst=os.path.join(self.package_folder, "include"))
+
+                        def package_info(self):
+                            self.cpp_info.components["qux"].includedirs = ["include"]
+                            self.cpp_info.components["baz"].libs = ["hello"]
+                            self.cpp_info.components["baz"].defines = ["FOOBAR=1"]
+                    ''')
+                     })
+        client.run('create . --name=hello --version=0.1.0 -c tools.microsoft.bash:subsystem=msys2 -c tools.microsoft.bash:path=bash')
+    with client.chdir("global"):
+        # Consume from global variables
+        client.run("install --requires=hello/0.1.0 -pr:b=default -pr:h=default -g MakeDeps -of build")
+        client.save({"Makefile": textwrap.dedent('''
+            include build/conandeps.mk
+            CXXFLAGS            += $(CONAN_CXXFLAGS)
+            CPPFLAGS            += $(addprefix -I, $(CONAN_INCLUDE_DIRS))
+            CPPFLAGS            += $(addprefix -D, $(CONAN_DEFINES))
+            LDFLAGS             += $(addprefix -L, $(CONAN_LIB_DIRS))
+            LDLIBS              += $(addprefix -l, $(CONAN_LIBS))
+            EXELINKFLAGS        += $(CONAN_EXELINKFLAGS)
+
+            all:
+            \t$(CXX) main.cpp $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) $(LDLIBS) $(EXELINKFLAGS) -o main
+            '''),
+            "main.cpp": gen_function_cpp(name="main", includes=["hello"], calls=["hello"]),
+            })
+        client.run_command("make -f Makefile")
+    with client.chdir("components"):
+        # Consume from components
+        client.run("install --requires=hello/0.1.0 -pr:b=default -pr:h=default -g MakeDeps -of build")
+        client.save({"Makefile": textwrap.dedent('''
+            include build/conandeps.mk
+            CXXFLAGS            += $(CONAN_CXXFLAGS)
+            CPPFLAGS            += $(addprefix -I, $(CONAN_INCLUDE_DIRS_HELLO_QUX))
+            CPPFLAGS            += $(addprefix -D, $(CONAN_DEFINES) $(CONAN_DEFINES_HELLO_BAZ))
+            LDFLAGS             += $(addprefix -L, $(CONAN_LIB_DIRS_HELLO_BAZ))
+            LDLIBS              += $(addprefix -l, $(CONAN_LIBS_HELLO_BAZ))
+            EXELINKFLAGS        += $(CONAN_EXELINKFLAGS)
+
+            all:
+            \t$(CXX) main.cpp $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) $(LDLIBS) $(EXELINKFLAGS) -o main
+            '''),
+            "main.cpp": gen_function_cpp(name="main", includes=["hello"], calls=["hello"]),
+            })
+        client.run_command("make -f Makefile")
diff --git a/conans/test/integration/toolchains/gnu/test_makedeps.py b/conans/test/integration/toolchains/gnu/test_makedeps.py
new file mode 100644
--- /dev/null
+++ b/conans/test/integration/toolchains/gnu/test_makedeps.py
@@ -0,0 +1,444 @@
+import platform
+import pytest
+import os
+import textwrap
+import pathlib
+
+from conans.test.assets.genconanfile import GenConanfile
+from conans.test.utils.tools import TestClient
+from conan.tools.gnu.makedeps import CONAN_MAKEFILE_FILENAME
+
+
+def test_make_dirs_with_abs_path():
+    """
+    MakeDeps should support absolute paths when cppinfp
+    """
+    conanfile = textwrap.dedent("""
+        import os
+        from conan import ConanFile
+
+        class TestMakeDirsConan(ConanFile):
+            name = "mylib"
+            version = "0.1"
+
+            def package_info(self):
+                self.cpp_info.frameworkdirs = []
+                libname = "mylib"
+                fake_dir = os.path.join("/", "my_absoulte_path", "fake")
+                include_dir = os.path.join(fake_dir, libname, "include")
+                lib_dir = os.path.join(fake_dir, libname, "lib")
+                lib_dir2 = os.path.join(self.package_folder, "lib2")
+                self.cpp_info.includedirs = [include_dir]
+                self.cpp_info.libdirs = [lib_dir, lib_dir2]
+        """)
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run("create .")
+    client.run("install --requires=mylib/0.1@ -g MakeDeps")
+
+    makefile_content = client.load(CONAN_MAKEFILE_FILENAME)
+    prefix = pathlib.Path.home().drive if platform.system() == "Windows" else ""
+    assert 'CONAN_NAME_MYLIB = mylib' in makefile_content
+    assert 'CONAN_VERSION_MYLIB = 0.1' in makefile_content
+    assert f'CONAN_LIB_DIRS_MYLIB = \\\n\t$(CONAN_LIB_DIR_FLAG){prefix}/my_absoulte_path/fake/mylib/lib \\\n\t$(CONAN_LIB_DIR_FLAG)$(CONAN_ROOT_MYLIB)/lib2' in makefile_content
+    assert f'CONAN_INCLUDE_DIRS_MYLIB = $(CONAN_INCLUDE_DIR_FLAG){prefix}/my_absoulte_path/fake/mylib/include' in makefile_content
+    assert 'CONAN_BIN_DIRS_MYLIB = $(CONAN_BIN_DIR_FLAG)$(CONAN_ROOT_MYLIB)/bin' in makefile_content
+
+    lines = makefile_content.splitlines()
+    for line_no, line in enumerate(lines):
+        includedir_pattern = "CONAN_INCLUDE_DIRS_MYLIB = $(CONAN_INCLUDE_DIRS_FLAG)"
+        if line.startswith(includedir_pattern):
+            assert os.path.isabs(line[len(includedir_pattern):])
+            assert line.endswith("include")
+        elif line.startswith("\t$(CONAN_LIB_DIRS_FLAG)") and 'my_absoulte_path' in line:
+            assert os.path.isabs(line[len("\t$(CONAN_LIB_DIRS_FLAG)"):-2])
+            assert line.endswith("lib \\")
+        elif line.startswith("\t$(CONAN_LIB_DIRS_FLAG)") and line.endswith('lib2'):
+            assert "\t$(CONAN_LIB_DIRS_FLAG)$(CONAN_ROOT_MYLIB)/lib2" in line
+
+
+def test_make_empty_dirs():
+    """
+    MakeDeps should support cppinfo empty dirs
+    """
+    conanfile = textwrap.dedent("""
+        import os
+        from conan import ConanFile
+
+        class TestMakeDepsConan(ConanFile):
+            name = "mylib"
+            version = "0.1"
+
+            def package_info(self):
+                self.cpp_info.includedirs = []
+                self.cpp_info.libdirs = []
+                self.cpp_info.bindirs = []
+                self.cpp_info.libs = []
+                self.cpp_info.frameworkdirs = []
+        """)
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run("create .")
+    client.run("install --requires=mylib/0.1@ -g MakeDeps")
+
+    makefile_content = client.load(CONAN_MAKEFILE_FILENAME)
+    assert 'CONAN_ROOT_MYLIB' in makefile_content
+    assert 'SYSROOT' not in makefile_content
+    assert 'CONAN_INCLUDE_DIRS' not in makefile_content
+    assert 'CONAN_LIB_DIRS' not in makefile_content
+    assert 'CONAN_BIN_DIRS' not in makefile_content
+    assert 'CONAN_LIBS' not in makefile_content
+    assert 'CONAN_FRAMEWORK_DIRS' not in makefile_content
+
+
+def test_libs_and_system_libs():
+    """
+    MakeDeps should support cppinfo system_libs with regular libs
+    """
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        from conan.tools.files import save
+        import os
+
+        class TestMakeDepsConan(ConanFile):
+            name = "mylib"
+            version = "0.1"
+
+            def package(self):
+                save(self, os.path.join(self.package_folder, "lib", "file"), "")
+
+            def package_info(self):
+                self.cpp_info.libs = ["mylib1", "mylib2"]
+                self.cpp_info.system_libs = ["system_lib1", "system_lib2"]
+        """)
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run("create .")
+    client.run("install --requires=mylib/0.1@ -g MakeDeps")
+
+    makefile_content = client.load(CONAN_MAKEFILE_FILENAME)
+    assert "CONAN_LIBS_MYLIB = \\\n\t$(CONAN_LIB_FLAG)mylib1 \\\n\t$(CONAN_LIB_FLAG)mylib2" in makefile_content
+    assert "CONAN_SYSTEM_LIBS_MYLIB = \\\n\t$(CONAN_SYSTEM_LIB_FLAG)system_lib1 \\\n\t$(CONAN_SYSTEM_LIB_FLAG)system_lib2" in makefile_content
+    assert "CONAN_LIBS = $(CONAN_LIBS_MYLIB)" in makefile_content
+    assert "CONAN_SYSTEM_LIBS = $(CONAN_SYSTEM_LIBS_MYLIB)" in makefile_content
+
+
+def test_multiple_include_and_lib_dirs():
+    """
+    MakeDeps should support cppinfo multiple include directories
+    """
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        from conan.tools.files import save
+        import os
+
+        class TestMakeDepsConan(ConanFile):
+            def package(self):
+                for p in ["inc1", "inc2", "inc3/foo", "lib1", "lib2"]:
+                    save(self, os.path.join(self.package_folder, p, "file"), "")
+
+            def package_info(self):
+                self.cpp_info.includedirs = ["inc1", "inc2", "inc3/foo"]
+                self.cpp_info.libdirs = ["lib1", "lib2"]
+        """)
+    client = TestClient()
+    client.save({"conanfile.py": conanfile})
+    client.run("create . --name=pkg --version=0.1")
+    client.run("install --requires=pkg/0.1@ -g MakeDeps")
+
+    makefile_content = client.load(CONAN_MAKEFILE_FILENAME)
+    assert "CONAN_INCLUDE_DIRS_PKG = \\\n" \
+           "\t$(CONAN_INCLUDE_DIR_FLAG)$(CONAN_ROOT_PKG)/inc1 \\\n" \
+           "\t$(CONAN_INCLUDE_DIR_FLAG)$(CONAN_ROOT_PKG)/inc2 \\\n" \
+           "\t$(CONAN_INCLUDE_DIR_FLAG)$(CONAN_ROOT_PKG)/inc3/foo\n" in makefile_content
+    assert "CONAN_LIB_DIRS_PKG = \\\n" \
+           "\t$(CONAN_LIB_DIR_FLAG)$(CONAN_ROOT_PKG)/lib1 \\\n" \
+           "\t$(CONAN_LIB_DIR_FLAG)$(CONAN_ROOT_PKG)/lib2\n" in makefile_content
+    assert "CONAN_INCLUDE_DIRS = $(CONAN_INCLUDE_DIRS_PKG)\n" in makefile_content
+    assert "CONAN_LIB_DIRS = $(CONAN_LIB_DIRS_PKG)\n" in makefile_content
+
+
+def test_make_with_public_deps_and_component_requires():
+    """
+    Testing a complex structure like:
+
+    * lib/0.1
+        - Components: "cmp1"
+    * other/0.1
+    * second/0.1
+        - Requires: "lib/0.1"
+        - Components: "mycomponent", "myfirstcomp"
+            + "mycomponent" requires "lib::cmp1"
+            + "myfirstcomp" requires "mycomponent"
+    * third/0.1
+        - Requires: "second/0.1", "other/0.1"
+    """
+    client = TestClient()
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+
+        class Recipe(ConanFile):
+
+            def package_info(self):
+                self.cpp_info.components["cmp1"].libs = ["libcmp1"]
+    """)
+    client.save({"conanfile.py": conanfile})
+    client.run("create . --name=lib --version=0.1")
+    client.save({"conanfile.py": GenConanfile("other", "0.1").with_package_file("file.h", "0.1")})
+    client.run("create .")
+
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+
+        class TestMakeDepsConan(ConanFile):
+            requires = "lib/0.1"
+
+            def package_info(self):
+                self.cpp_info.components["mycomponent"].requires.append("lib::cmp1")
+                self.cpp_info.components["myfirstcomp"].requires.append("mycomponent")
+
+        """)
+    client.save({"conanfile.py": conanfile}, clean_first=True)
+    client.run("create . --name=second --version=0.1")
+    client.save({"conanfile.py": GenConanfile("third", "0.1").with_package_file("file.h", "0.1")
+                                                             .with_require("second/0.1")
+                                                             .with_require("other/0.1")},
+                clean_first=True)
+    client.run("create .")
+
+    client2 = TestClient(cache_folder=client.cache_folder)
+    conanfile = textwrap.dedent("""
+        [requires]
+        third/0.1
+
+        [generators]
+        MakeDeps
+        """)
+    client2.save({"conanfile.txt": conanfile})
+    client2.run("install .")
+
+    makefile_content = client2.load(CONAN_MAKEFILE_FILENAME)
+    assert "CONAN_DEPS = \\\n" \
+           "\tthird \\\n" \
+           "\tsecond \\\n" \
+           "\tlib \\\n" \
+           "\tother\n" in makefile_content
+    assert 'CONAN_REQUIRES_SECOND = \\\n' \
+           '\t$(CONAN_REQUIRES_SECOND_MYCOMPONENT) \\\n' \
+           '\t$(CONAN_REQUIRES_SECOND_MYFIRSTCOMP)\n' in makefile_content
+    assert 'SYSROOT' not in makefile_content
+    assert 'CONAN_REQUIRES_SECOND_MYFIRSTCOMP = mycomponent\n' in makefile_content
+    assert 'CONAN_LIBS_LIB = $(CONAN_LIBS_LIB_CMP1)\n'in makefile_content
+
+    assert 'CONAN_COMPONENTS_LIB = cmp1\n' in makefile_content
+    assert 'CONAN_LIBS_LIB_CMP1 = $(CONAN_LIB_FLAG)libcmp1\n' in makefile_content
+    assert 'CONAN_REQUIRES = $(CONAN_REQUIRES_SECOND)\n' in makefile_content
+    assert 'CONAN_LIBS = $(CONAN_LIBS_LIB)\n' in makefile_content
+
+
+def test_make_with_public_deps_and_component_requires_second():
+    """
+    Testing another complex structure like:
+
+    * other/0.1
+        - Components: "cmp1", "cmp2", "cmp3"
+            + "cmp1" (it shouldn't be affected by "other")
+            + "cmp3" (it shouldn't be affected by "other")
+            + "cmp3" requires "cmp1"
+    * pkg/0.1
+        - Requires: "other/0.1" -> "other::cmp1"
+    """
+    client = TestClient()
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+
+        class Recipe(ConanFile):
+
+            def package_info(self):
+                self.cpp_info.components["cmp1"].libs = ["other_cmp1"]
+                self.cpp_info.components["cmp2"].libs = ["other_cmp2"]
+                self.cpp_info.components["cmp3"].requires.append("cmp1")
+    """)
+    client.save({"conanfile.py": conanfile})
+    client.run("create . --name=other --version=1.0")
+
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+
+        class TestMakeDepsConan(ConanFile):
+            requires = "other/1.0"
+
+            def package_info(self):
+                self.cpp_info.requires = ["other::cmp1"]
+        """)
+    client.save({"conanfile.py": conanfile})
+    client.run("create . --name=pkg --version=0.1")
+
+    client2 = TestClient(cache_folder=client.cache_folder)
+    conanfile = textwrap.dedent("""
+        [requires]
+        pkg/0.1
+
+        [generators]
+        MakeDeps
+        """)
+    client2.save({"conanfile.txt": conanfile})
+    client2.run("install .")
+    make_content = client2.load(CONAN_MAKEFILE_FILENAME)
+    assert 'CONAN_REQUIRES_PKG = other::cmp1\n' in make_content
+    assert 'CONAN_REQUIRES_OTHER = $(CONAN_REQUIRES_OTHER_CMP3)\n' in make_content
+    assert 'CONAN_REQUIRES_OTHER_CMP3 = cmp1\n' in make_content
+    assert 'CONAN_COMPONENTS_OTHER = \\\n\tcmp1 \\\n\tcmp2 \\\n\tcmp3\n' in make_content
+    assert 'CONAN_LIBS_OTHER = \\\n\t$(CONAN_LIBS_OTHER_CMP1) \\\n\t$(CONAN_LIBS_OTHER_CMP2)\n' in make_content
+    assert 'CONAN_LIBS_OTHER_CMP1 = $(CONAN_LIB_FLAG)other_cmp1\n' in make_content
+    assert 'CONAN_LIBS_OTHER_CMP2 = $(CONAN_LIB_FLAG)other_cmp2\n' in make_content
+    assert 'CONAN_LIBS = $(CONAN_LIBS_OTHER)\n' in make_content
+    assert 'CONAN_REQUIRES = \\\n\t$(CONAN_REQUIRES_PKG) \\\n\t$(CONAN_REQUIRES_OTHER)\n' in make_content
+
+
+def test_makedeps_with_test_requires():
+    """
+    MakeDeps has to create any test requires to be declared on the recipe.
+    """
+    client = TestClient()
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        class TestMakeDeps(ConanFile):
+            def package_info(self):
+                self.cpp_info.libs = [f"lib{self.name}"]
+        """)
+
+    client.save({"conanfile.py": conanfile})
+    client.run("create . --name=app --version=1.0")
+    client.run("create . --name=test --version=1.0")
+    # Create library having build and test requires
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        class HelloLib(ConanFile):
+            def build_requirements(self):
+                self.tool_requires('app/1.0')
+                self.test_requires('test/1.0')
+        """)
+    client.save({"conanfile.py": conanfile}, clean_first=True)
+    client.run("install . -g MakeDeps")
+    assert 'CONAN_DEPS = test\n' in client.load(CONAN_MAKEFILE_FILENAME)
+    assert 'app' not in client.load(CONAN_MAKEFILE_FILENAME)
+
+
+def test_makedeps_with_editable_layout():
+    """
+    The MakeDeps should be supported with editable layour mode
+    """
+    client = TestClient()
+    dep = textwrap.dedent("""
+        from conan import ConanFile
+        from conan.tools.files import save
+        class Dep(ConanFile):
+            name = "dep"
+            version = "0.1"
+            def layout(self):
+                self.cpp.source.includedirs = ["include"]
+            def package_info(self):
+                self.cpp_info.libs = ["mylib"]
+        """)
+    client.save({"dep/conanfile.py": dep,
+                 "dep/include/header.h": "",
+                 "pkg/conanfile.py": GenConanfile("pkg", "0.1").with_requires("dep/0.1")})
+    client.run("create dep")
+    client.run("editable add dep")
+    with client.chdir("pkg"):
+        client.run("install . -g MakeDeps")
+        makefile_content = client.load(CONAN_MAKEFILE_FILENAME)
+        assert 'CONAN_LIBS_DEP = $(CONAN_LIB_FLAG)mylib\n' in makefile_content
+        assert 'CONAN_INCLUDE_DIRS_DEP = $(CONAN_INCLUDE_DIR_FLAG)$(CONAN_ROOT_DEP)/include\n' in makefile_content
+
+
+def test_makedeps_tool_requires():
+    """
+    Testing if MakeDeps are created for tool requires
+    """
+    client = TestClient()
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+
+        class TestMakeDepsConan(ConanFile):
+
+            def package_info(self):
+                self.cpp_info.libs = ["libtool"]
+        """)
+    client.save({"conanfile.py": conanfile})
+    client.run("create . --name tool --version 1.0")
+
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+
+        class TestMakeDepsConan(ConanFile):
+
+            def package_info(self):
+                self.cpp_info.components["cmp1"].libs = ["other_cmp1"]
+                self.cpp_info.components["cmp2"].libs = ["other_cmp2"]
+                self.cpp_info.components["cmp3"].requires.append("cmp1")
+        """)
+    client.save({"conanfile.py": conanfile}, clean_first=True)
+    client.run("create . --name other --version 1.0")
+
+    conanfile = textwrap.dedent("""
+        [tool_requires]
+        tool/1.0
+        other/1.0
+        [generators]
+        MakeDeps
+        """)
+    client.save({"conanfile.txt": conanfile}, clean_first=True)
+    client.run("install . -pr:h default -pr:b default")
+    make_content = client.load(CONAN_MAKEFILE_FILENAME)
+
+    # TODO: MakeDeps should support tool_requires in the future
+    assert "CONAN_LIBS_TOOL" not in make_content
+    assert "CONAN_NAME_OTHER" not in make_content
+
+
+@pytest.mark.parametrize("pattern, result, expected",
+                         [("libs = []", False, 'SYSROOT'),
+                          ("sysroot = ['/foo/bar/sysroot']", True, 'CONAN_SYSROOT_PACKAGE = /foo/bar/sysroot')])
+def test_makefile_sysroot(pattern, result, expected):
+    """
+    The MakeDeps should not enforce sysroot in case not defined
+    """
+    client = TestClient()
+    conanfile = textwrap.dedent("""
+            from conan import ConanFile
+            class TestMakeDepsConan(ConanFile):
+                def package_info(self):
+                    self.cpp_info.{{ pattern }}
+            """)
+    client.save({"conanfile.py": conanfile.replace("{{ pattern }}", pattern)})
+    client.run("create . --name package --version 0.1.0")
+    client.run("install --requires=package/0.1.0 -pr:h default -pr:b default -g MakeDeps")
+    makefile_content = client.load(CONAN_MAKEFILE_FILENAME)
+    assert (expected in makefile_content) == result
+
+
+def test_makefile_reference():
+    """
+    The MakeDeps should generate the correct package reference as variable
+    """
+    client = TestClient()
+    conanfile = textwrap.dedent("""
+            from conan import ConanFile
+            class Testing(ConanFile):
+                pass
+            """)
+    client.save({"conanfile.py": conanfile})
+
+    # official packages <name>/<version>
+    client.run("create . --name package --version 0.1.0")
+    client.run("install --requires=package/0.1.0 -pr:h default -pr:b default -g MakeDeps")
+    makefile_content = client.load(CONAN_MAKEFILE_FILENAME)
+    assert 'CONAN_REFERENCE_PACKAGE = package/0.1.0\n' in makefile_content
+
+    # custom packages <name>/<version>@<user>/<channel>
+    client.run("create . --name package --version 0.1.0 --user user --channel channel")
+    client.run("install --requires=package/0.1.0@user/channel -pr:h default -pr:b default -g MakeDeps")
+    makefile_content = client.load(CONAN_MAKEFILE_FILENAME)
+    assert 'CONAN_REFERENCE_PACKAGE = package/0.1.0@user/channel\n' in makefile_content