Repository: conan-io/conan
Pull request #12516 (2022-11-11 18:34:09)

propose new NMakeToolchain
Changelog: Feature: New NMakeToolchain, environment based
Docs: https://github.com/conan-io/docs/pull/XXXX

Partially address https://github.com/conan-io/conan/issues/12188



Patch:
diff --git a/conan/tools/cmake/cmakedeps/templates/target_data.py b/conan/tools/cmake/cmakedeps/templates/target_data.py
index 61011bb0c..cb4b733b0 100644
--- a/conan/tools/cmake/cmakedeps/templates/target_data.py
+++ b/conan/tools/cmake/cmakedeps/templates/target_data.py
@@ -44,15 +44,13 @@ class ConfigDataTemplate(CMakeDepsFileTemplate):
         dependency_filenames = self._get_dependency_filenames()
         # Get the nodes that have the property cmake_find_mode=None (no files to generate)
         dependency_find_modes = self._get_dependencies_find_modes()
-        # package_folder might not be defined if Editable and layout()
-        package_folder = self.conanfile.package_folder or ""
-        package_folder = package_folder.replace('\\', '/').replace('$', '\\$').replace('"', '\\"')
+        root_folder = self._root_folder.replace('\\', '/').replace('$', '\\$').replace('"', '\\"')
 
         return {"global_cpp": global_cpp,
                 "has_components": self.conanfile.cpp_info.has_components,
                 "pkg_name": self.pkg_name,
                 "file_name": self.file_name,
-                "package_folder": package_folder,
+                "package_folder": root_folder,
                 "config_suffix": self.config_suffix,
                 "components_names": components_names,
                 "components_cpp": components_cpp,
@@ -139,7 +137,12 @@ class ConfigDataTemplate(CMakeDepsFileTemplate):
     def _get_global_cpp_cmake(self):
         global_cppinfo = self.conanfile.cpp_info.aggregated_components()
         pfolder_var_name = "{}_PACKAGE_FOLDER{}".format(self.pkg_name, self.config_suffix)
-        return _TargetDataContext(global_cppinfo, pfolder_var_name, self.conanfile.package_folder)
+        return _TargetDataContext(global_cppinfo, pfolder_var_name, self._root_folder)
+
+    @property
+    def _root_folder(self):
+        return self.conanfile.recipe_folder if self.conanfile.package_folder is None \
+            else self.conanfile.package_folder
 
     def _get_required_components_cpp(self):
         """Returns a list of (component_name, DepsCppCMake)"""
@@ -149,8 +152,7 @@ class ConfigDataTemplate(CMakeDepsFileTemplate):
         direct_visible_host = self.conanfile.dependencies.filter({"build": False, "visible": True,
                                                                   "direct": True})
         for comp_name, comp in sorted_comps.items():
-            deps_cpp_cmake = _TargetDataContext(comp, pfolder_var_name,
-                                                self.conanfile.package_folder)
+            deps_cpp_cmake = _TargetDataContext(comp, pfolder_var_name, self._root_folder)
             public_comp_deps = []
             for require in comp.requires:
                 if "::" in require:  # Points to a component of a different package
@@ -216,9 +218,7 @@ class _TargetDataContext(object):
                 assert os.path.isabs(p), "{} is not absolute".format(p)
 
                 # Trying to use a ${mypkg_PACKAGE_FOLDER}/include path instead of full
-                # TODO: We could do something similar for some build_folder for editables
-                # package_folder can be None if editable and layout()
-                if package_folder is not None and p.startswith(package_folder):
+                if p.startswith(package_folder):
                     # Prepend the {{ pkg_name }}_PACKAGE_FOLDER{{ config_suffix }}
                     rel = p[len(package_folder):]
                     rel = rel.replace('\\', '/').replace('$', '\\$').replace('"', '\\"').lstrip("/")
@@ -241,13 +241,6 @@ class _TargetDataContext(object):
                                    replace('"', '\\"'))
                                    for v in values)
 
-        def join_paths_single_var(values):
-            """
-            semicolon-separated list of dirs:
-            e.g: set(LIBFOO_INCLUDE_DIR "/path/to/included/dir;/path/to/included/dir2")
-            """
-            return '"%s"' % ";".join(p.replace('\\', '/').replace('$', '\\$') for p in values)
-
         self.include_paths = join_paths(cpp_info.includedirs)
         self.lib_paths = join_paths(cpp_info.libdirs)
         self.res_paths = join_paths(cpp_info.resdirs)
diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py
index eda75b0d6..698e050d2 100644
--- a/conan/tools/cmake/toolchain/blocks.py
+++ b/conan/tools/cmake/toolchain/blocks.py
@@ -12,6 +12,7 @@ from conan.tools.build.cross_building import cross_building
 from conan.tools.cmake.toolchain import CONAN_TOOLCHAIN_FILENAME
 from conan.tools.intel import IntelCC
 from conan.tools.microsoft.visual import is_msvc, msvc_version_to_toolset_version
+from conans.client.subsystems import deduce_subsystem, WINDOWS
 from conans.errors import ConanException
 from conans.util.files import load
 
@@ -153,7 +154,7 @@ class FPicBlock(Block):
     template = textwrap.dedent("""
         {% if fpic %}
         message(STATUS "Conan toolchain: Setting CMAKE_POSITION_INDEPENDENT_CODE={{ fpic }} (options.fPIC)")
-        set(CMAKE_POSITION_INDEPENDENT_CODE {{ fpic }} CACHE BOOL "Position independent code") 
+        set(CMAKE_POSITION_INDEPENDENT_CODE {{ fpic }} CACHE BOOL "Position independent code")
         {% endif %}
         """)
 
@@ -559,6 +560,34 @@ class FindFiles(Block):
         }
 
 
+class PkgConfigBlock(Block):
+    template = textwrap.dedent("""
+        {% if pkg_config %}
+        set(PKG_CONFIG_EXECUTABLE {{ pkg_config }} CACHE FILEPATH "pkg-config executable")
+        {% endif %}
+        {% if pkg_config_path %}
+        if (DEFINED ENV{PKG_CONFIG_PATH})
+        set(ENV{PKG_CONFIG_PATH} "{{ pkg_config_path }}$ENV{PKG_CONFIG_PATH}")
+        else()
+        set(ENV{PKG_CONFIG_PATH} "{{ pkg_config_path }}")
+        endif()
+        {% endif %}
+        """)
+
+    def context(self):
+        pkg_config = self._conanfile.conf.get("tools.gnu:pkg_config", check_type=str)
+        if pkg_config:
+            pkg_config = pkg_config.replace("\\", "/")
+        pkg_config_path = self._conanfile.generators_folder
+        if pkg_config_path:
+            # hardcoding scope as "build"
+            subsystem = deduce_subsystem(self._conanfile, "build")
+            pathsep = ":" if subsystem != WINDOWS else ";"
+            pkg_config_path = pkg_config_path.replace("\\", "/") + pathsep
+        return {"pkg_config": pkg_config,
+                "pkg_config_path": pkg_config_path}
+
+
 class UserToolchain(Block):
     template = textwrap.dedent("""
         {% for user_toolchain in paths %}
diff --git a/conan/tools/cmake/toolchain/toolchain.py b/conan/tools/cmake/toolchain/toolchain.py
index 310648c41..599a9338c 100644
--- a/conan/tools/cmake/toolchain/toolchain.py
+++ b/conan/tools/cmake/toolchain/toolchain.py
@@ -11,8 +11,8 @@ from conan.tools.cmake.presets import write_cmake_presets
 from conan.tools.cmake.toolchain import CONAN_TOOLCHAIN_FILENAME
 from conan.tools.cmake.toolchain.blocks import ToolchainBlocks, UserToolchain, GenericSystemBlock, \
     AndroidSystemBlock, AppleSystemBlock, FPicBlock, ArchitectureBlock, GLibCXXBlock, VSRuntimeBlock, \
-    CppStdBlock, ParallelBlock, CMakeFlagsInitBlock, TryCompileBlock, FindFiles, SkipRPath, \
-    SharedLibBock, OutputDirsBlock, ExtraFlagsBlock
+    CppStdBlock, ParallelBlock, CMakeFlagsInitBlock, TryCompileBlock, FindFiles, PkgConfigBlock, \
+    SkipRPath, SharedLibBock, OutputDirsBlock, ExtraFlagsBlock
 from conan.tools.intel import IntelCC
 from conan.tools.microsoft import VCVars
 from conan.tools.microsoft.visual import vs_ide_version
@@ -139,6 +139,7 @@ class CMakeToolchain(object):
                                        ("cmake_flags_init", CMakeFlagsInitBlock),
                                        ("try_compile", TryCompileBlock),
                                        ("find_paths", FindFiles),
+                                       ("pkg_config", PkgConfigBlock),
                                        ("rpath", SkipRPath),
                                        ("shared", SharedLibBock),
                                        ("output_dirs", OutputDirsBlock)])
diff --git a/conan/tools/env/environment.py b/conan/tools/env/environment.py
index 4d43ed19a..a4b96ea9f 100644
--- a/conan/tools/env/environment.py
+++ b/conan/tools/env/environment.py
@@ -251,16 +251,34 @@ class EnvVars:
     def keys(self):
         return self._values.keys()
 
-    def get(self, name, default=None):
+    def get(self, name, default=None, variable_reference=None):
+        """
+        :param name: The name of the environment variable
+        :param default: The returned value if the variable doesn't exist, by default None
+        :param variable_reference: if specified, use a variable reference instead of the
+        pre-existing value of environment variable, where {name} can be used to refer to the
+        name of the variable.
+        """
         v = self._values.get(name)
         if v is None:
             return default
-        return v.get_value(self._subsystem, self._pathsep)
+        if variable_reference:
+            return v.get_str(variable_reference, self._subsystem, self._pathsep)
+        else:
+            return v.get_value(self._subsystem, self._pathsep)
 
-    def items(self):
-        """returns {str: str} (varname: value)"""
-        return {k: v.get_value(self._subsystem, self._pathsep)
-                for k, v in self._values.items()}.items()
+    def items(self, variable_reference=None):
+        """returns {str: str} (varname: value)
+         :param variable_reference: if specified, use a variable reference instead of the
+        pre-existing value of environment variable, where {name} can be used to refer to the
+        name of the variable.
+        """
+        if variable_reference:
+            return {k: v.get_str(variable_reference, self._subsystem, self._pathsep)
+                    for k, v in self._values.items()}.items()
+        else:
+            return {k: v.get_value(self._subsystem, self._pathsep)
+                    for k, v in self._values.items()}.items()
 
     @contextmanager
     def apply(self):
diff --git a/conan/tools/gnu/autotools.py b/conan/tools/gnu/autotools.py
index 1992ba20d..a3a7e7449 100644
--- a/conan/tools/gnu/autotools.py
+++ b/conan/tools/gnu/autotools.py
@@ -1,4 +1,5 @@
 import os
+import re
 
 from conan.tools.build import build_jobs
 from conan.tools.files.files import load_toolchain_args
@@ -48,7 +49,8 @@ class Autotools(object):
         str_args = self._make_args
         str_extra_args = " ".join(args) if args is not None else ""
         jobs = ""
-        if "-j" not in str_args and "nmake" not in make_program.lower():
+        jobs_already_passed = re.search(r"(^-j\d+)|(\W-j\d+\s*)", join_arguments([str_args, str_extra_args]))
+        if not jobs_already_passed and "nmake" not in make_program.lower():
             njobs = build_jobs(self._conanfile)
             if njobs:
                 jobs = "-j{}".format(njobs)
@@ -56,8 +58,10 @@ class Autotools(object):
         self._conanfile.run(command)
 
     def install(self, args=None, target="install"):
-        args = args if args is not None else \
-            ["DESTDIR={}".format(unix_path(self._conanfile, self._conanfile.package_folder))]
+        args = args if args else []
+        str_args = " ".join(args)
+        if "DESTDIR=" not in str_args:
+            args.insert(0, "DESTDIR={}".format(unix_path(self._conanfile, self._conanfile.package_folder)))
         self.make(target=target, args=args)
 
     def autoreconf(self, args=None):
diff --git a/conan/tools/gnu/pkgconfigdeps.py b/conan/tools/gnu/pkgconfigdeps.py
index 38a7737d6..122e667b1 100644
--- a/conan/tools/gnu/pkgconfigdeps.py
+++ b/conan/tools/gnu/pkgconfigdeps.py
@@ -157,10 +157,12 @@ class _PCContentGenerator:
     def content(self, info):
         assert isinstance(info, _PCInfo) and info.cpp_info is not None
 
-        package_folder = self._dep.package_folder or ""  # If editable, package_folder can be None
+        # If editable, package_folder can be None
+        root_folder = self._dep.recipe_folder if self._dep.package_folder is None \
+            else self._dep.package_folder
         version = info.cpp_info.get_property("component_version") or self._dep.ref.version
 
-        prefix_path = package_folder.replace("\\", "/")
+        prefix_path = root_folder.replace("\\", "/")
         libdirs = _get_formatted_dirs(info.cpp_info.libdirs, prefix_path)
         includedirs = _get_formatted_dirs(info.cpp_info.includedirs, prefix_path)
         custom_content = info.cpp_info.get_property("pkg_config_custom_content")
diff --git a/conan/tools/google/bazeldeps.py b/conan/tools/google/bazeldeps.py
index c6966c043..4436993de 100644
--- a/conan/tools/google/bazeldeps.py
+++ b/conan/tools/google/bazeldeps.py
@@ -146,7 +146,7 @@ class BazelDeps(object):
         lib_dir = 'lib'
 
         dependencies = []
-        for req, dep in dependency.dependencies.items():
+        for dep in dependency.dependencies.direct_host.values():
             dependencies.append(dep.ref.name)
 
         shared_library = dependency.options.get_safe("shared") if dependency.options else False
diff --git a/conan/tools/microsoft/__init__.py b/conan/tools/microsoft/__init__.py
index c956ae068..e0c0c339a 100644
--- a/conan/tools/microsoft/__init__.py
+++ b/conan/tools/microsoft/__init__.py
@@ -3,5 +3,6 @@ from conan.tools.microsoft.msbuild import MSBuild
 from conan.tools.microsoft.msbuilddeps import MSBuildDeps
 from conan.tools.microsoft.subsystems import unix_path
 from conan.tools.microsoft.toolchain import MSBuildToolchain
+from conan.tools.microsoft.nmaketoolchain import NMakeToolchain
 from conan.tools.microsoft.visual import msvc_runtime_flag, VCVars, is_msvc, \
     is_msvc_static_runtime, check_min_vs
diff --git a/conan/tools/microsoft/msbuilddeps.py b/conan/tools/microsoft/msbuilddeps.py
index 3c3d2739c..2053d38a0 100644
--- a/conan/tools/microsoft/msbuilddeps.py
+++ b/conan/tools/microsoft/msbuilddeps.py
@@ -158,17 +158,18 @@ class MSBuildDeps(object):
             for p in paths:
                 assert os.path.isabs(p), "{} is not absolute".format(p)
                 full_path = escape_path(p)
-                if full_path.startswith(package_folder):
-                    rel = full_path[len(package_folder)+1:]
+                if full_path.startswith(root_folder):
+                    rel = full_path[len(root_folder)+1:]
                     full_path = ("%s/%s" % (pkg_placeholder, rel))
                 ret.append(full_path)
             return "".join("{};".format(e) for e in ret)
 
-        package_folder = escape_path(dep.package_folder)
+        root_folder = dep.recipe_folder if dep.package_folder is None else dep.package_folder
+        root_folder = escape_path(root_folder)
 
         fields = {
             'name': name,
-            'root_folder': package_folder,
+            'root_folder': root_folder,
             'bin_dirs': join_paths(cpp_info.bindirs),
             'res_dirs': join_paths(cpp_info.resdirs),
             'include_dirs': join_paths(cpp_info.includedirs),
diff --git a/conan/tools/microsoft/nmaketoolchain.py b/conan/tools/microsoft/nmaketoolchain.py
new file mode 100644
index 000000000..ef4795385
--- /dev/null
+++ b/conan/tools/microsoft/nmaketoolchain.py
@@ -0,0 +1,56 @@
+from conan.tools._compilers import cppstd_flag, build_type_flags
+from conan.tools.env import Environment
+
+
+class NMakeToolchain(object):
+    """
+    https://learn.microsoft.com/en-us/cpp/build/reference/running-nmake?view=msvc-170#toolsini-and-nmake
+    We have also explored the usage of Tools.ini:
+    https://learn.microsoft.com/en-us/cpp/build/reference/running-nmake?view=msvc-170
+    but not possible, because it cannot include other files, it will also potentially collide with
+    a user Tool.ini, without easy resolution. At least the environment is additive.
+    """
+    def __init__(self, conanfile):
+        """
+        :param conanfile: ``< ConanFile object >`` The current recipe object. Always use ``self``.
+        """
+        self._conanfile = conanfile
+        self._environment = None
+
+    @property
+    def cl_flags(self):
+        cppflags = []
+        build_type = self._conanfile.settings.get_safe("build_type")
+        if build_type in ['Release', 'RelWithDebInfo', 'MinSizeRel']:
+            cppflags.append("/DNDEBUG")
+
+        bt = build_type_flags(self._conanfile.settings)
+        if bt:
+            cppflags.extend(bt)
+
+        cppstd = cppstd_flag(self._conanfile.settings)
+        if cppstd:
+            cppflags.append(cppstd)
+        from conan.tools.microsoft import msvc_runtime_flag
+        flag = msvc_runtime_flag(self._conanfile)
+        if flag:
+            cppflags.append("-{}".format(flag))
+        return " ".join(cppflags).replace("-", "/")
+
+    @property
+    def environment(self):
+        # TODO: Seems we want to make this uniform, equal to other generators
+        if self._environment is None:
+            env = Environment()
+            # The whole injection of toolchain happens in CL env-var, the others LIBS, _LINK_
+            env.append("CL", self.cl_flags)
+            self._environment = env
+        return self._environment
+
+    def vars(self, scope="build"):
+        return self.environment.vars(self._conanfile, scope=scope)
+
+    def generate(self, scope="build"):
+        self.vars(scope).save_script("conannmaketoolchain")
+        from conan.tools.microsoft import VCVars
+        VCVars(self._conanfile).generate()
diff --git a/conans/client/generators/__init__.py b/conans/client/generators/__init__.py
index 86d19ae10..97838c290 100644
--- a/conans/client/generators/__init__.py
+++ b/conans/client/generators/__init__.py
@@ -73,7 +73,7 @@ class GeneratorManager(object):
                                 "VirtualRunEnv", "VirtualBuildEnv", "AutotoolsDeps",
                                 "AutotoolsToolchain", "BazelDeps", "BazelToolchain", "PkgConfigDeps",
                                 "VCVars", "IntelCC", "XcodeDeps", "PremakeDeps", "XcodeToolchain",
-                                "MesonDeps"]
+                                "MesonDeps", "NMakeToolchain"]
 
     def add(self, name, generator_class, custom=False):
         if name not in self._generators or custom:
@@ -150,6 +150,9 @@ class GeneratorManager(object):
         elif generator_name == "XcodeToolchain":
             from conan.tools.apple import XcodeToolchain
             return XcodeToolchain
+        elif generator_name == "NMakeToolchain":
+            from conan.tools.microsoft import NMakeToolchain
+            return NMakeToolchain
         else:
             raise ConanException("Internal Conan error: Generator '{}' "
                                  "not commplete".format(generator_name))
diff --git a/conans/client/subsystems.py b/conans/client/subsystems.py
index 7b961d815..aea3bf1d5 100644
--- a/conans/client/subsystems.py
+++ b/conans/client/subsystems.py
@@ -75,10 +75,18 @@ def _windows_bash_wrapper(conanfile, command, env, envfiles_folder):
         # Configure MSYS2 to inherith the PATH
         msys2_mode_env = Environment()
         _msystem = {"x86": "MINGW32"}.get(conanfile.settings.get_safe("arch"), "MINGW64")
+        # https://www.msys2.org/wiki/Launchers/ dictates that the shell should be launched with
+        # - MSYSTEM defined
+        # - CHERE_INVOKING is necessary to keep the CWD and not change automatically to the user home
         msys2_mode_env.define("MSYSTEM", _msystem)
         msys2_mode_env.define("MSYS2_PATH_TYPE", "inherit")
+        # So --login do not change automatically to the user home
+        msys2_mode_env.define("CHERE_INVOKING", "1")
         path = os.path.join(conanfile.generators_folder, "msys2_mode.bat")
+        # Make sure we save pure .bat files, without sh stuff
+        wb, conanfile.win_bash = conanfile.win_bash, None
         msys2_mode_env.vars(conanfile, "build").save_bat(path)
+        conanfile.win_bash = wb
         env.append(path)
 
     wrapped_shell = '"%s"' % shell_path if " " in shell_path else shell_path
@@ -91,7 +99,9 @@ def _windows_bash_wrapper(conanfile, command, env, envfiles_folder):
                                                 accepted_extensions=("sh", ))
     wrapped_user_cmd = _escape_windows_cmd(wrapped_user_cmd)
 
-    final_command = '{} -c {}'.format(wrapped_shell, wrapped_user_cmd)
+    # according to https://www.msys2.org/wiki/Launchers/, it is necessary to use --login shell
+    # running without it is discouraged
+    final_command = '{} --login -c {}'.format(wrapped_shell, wrapped_user_cmd)
     return final_command
 
 


Test patch:
diff --git a/conans/test/functional/layout/test_editable_msbuild.py b/conans/test/functional/layout/test_editable_msbuild.py
index a06d8779e..4773d72ae 100644
--- a/conans/test/functional/layout/test_editable_msbuild.py
+++ b/conans/test/functional/layout/test_editable_msbuild.py
@@ -1,8 +1,10 @@
 import os
 import platform
+import textwrap
 
 import pytest
 
+from conans.test.assets.genconanfile import GenConanfile
 from conans.test.assets.pkg_cmake import pkg_cmake_app
 from conans.test.utils.tools import TestClient
 
@@ -62,3 +64,25 @@ def test_editable_msbuild():
     c.run("create pkg")
     # print(c.out)
     assert "pkg/0.1: Created package" in c.out
+
+
+@pytest.mark.skipif(platform.system() != "Windows", reason="Only for windows")
+def test_editable_msbuilddeps():
+    # https://github.com/conan-io/conan/issues/12521
+    c = TestClient()
+    dep = textwrap.dedent("""
+        from conan import ConanFile
+        class Dep(ConanFile):
+            name = "dep"
+            version = "0.1"
+            def layout(self):
+                pass
+            """)
+    c.save({"dep/conanfile.py": dep,
+            "pkg/conanfile.py": GenConanfile("pkg", "0.1").with_settings("build_type", "arch")
+                                                          .with_requires("dep/0.1")
+                                                          .with_generator("MSBuildDeps")})
+    c.run("editable add dep dep/0.1")
+    c.run("install pkg")
+    # It doesn't crash anymore
+    assert "dep/0.1:5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9 - Editable" in c.out
diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
index 397aac643..49f73cf5a 100644
--- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
+++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
@@ -1099,7 +1099,7 @@ def test_cmake_toolchain_vars_when_option_declared():
 
     t.run("new mylib/1.0 --template cmake_lib")
     t.save({"CMakeLists.txt": cmakelists})
-    
+
     # The generated toolchain should set `BUILD_SHARED_LIBS` to `OFF`,
     # and `CMAKE_POSITION_INDEPENDENT_CODE` to `ON` and the calls to
     # `option()` in the CMakeLists.txt should respect the existing values.
@@ -1112,7 +1112,7 @@ def test_cmake_toolchain_vars_when_option_declared():
     assert "mylib target type: STATIC_LIBRARY" in t.out
     assert f"mylib position independent code: {fpic_cmake_value}" in t.out
 
-    # When building manually, ensure the value passed by the toolchain overrides the ones in 
+    # When building manually, ensure the value passed by the toolchain overrides the ones in
     # the CMakeLists
     fpic_option = "-o mylib:fPIC=False" if platform.system() != "Windows" else ""
     t.run(f"install . -o mylib:shared=False {fpic_option}")
@@ -1198,10 +1198,10 @@ def test_find_program_for_tool_requires():
 
             def layout(self):
                 cmake_layout(self)
-            
+
             def requirements(self):
                 self.requires("foobar/1.0")
-            
+
             def build_requirements(self):
                 self.tool_requires("foobar/1.0")
     """)
@@ -1227,7 +1227,63 @@ def test_find_program_for_tool_requires():
 
     with client.chdir("build"):
         client.run_command("cmake .. -DCMAKE_TOOLCHAIN_FILE=generators/conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release")
-        # Verify binary executable is found from build context package, 
+        # Verify binary executable is found from build context package,
         # and library comes from host context package
         assert f"package/{build_context_package_id}/bin/foobin" in client.out
         assert f"package/{host_context_package_id}/include" in client.out
+
+
+@pytest.mark.tool_pkg_config
+def test_cmaketoolchain_and_pkg_config_path():
+    """
+    Lightweight test which is loading a dependency as a *.pc file through
+    CMake thanks to pkg_check_modules macro.
+    It's working because PKG_CONFIG_PATH env variable is being loaded automatically
+    by the CMakeToolchain generator.
+    """
+    client = TestClient()
+    dep = textwrap.dedent("""
+    from conan import ConanFile
+    class DepConan(ConanFile):
+        name = "dep"
+        version = "1.0"
+        def package_info(self):
+            self.cpp_info.libs = ["dep"]
+    """)
+    pkg = textwrap.dedent("""
+    from conan import ConanFile
+    from conan.tools.cmake import CMake, cmake_layout
+    class HelloConan(ConanFile):
+        name = "pkg"
+        version = "1.0"
+        settings = "os", "compiler", "build_type", "arch"
+        generators = "PkgConfigDeps", "CMakeToolchain"
+        exports_sources = "CMakeLists.txt"
+
+        def requirements(self):
+            self.requires("dep/1.0")
+
+        def layout(self):
+            cmake_layout(self)
+
+        def build(self):
+            cmake = CMake(self)
+            cmake.configure()
+            cmake.build()
+    """)
+    cmakelists = textwrap.dedent("""
+    cmake_minimum_required(VERSION 3.15)
+    project(pkg CXX)
+
+    find_package(PkgConfig REQUIRED)
+    # We should have PKG_CONFIG_PATH created in the current environment
+    pkg_check_modules(DEP REQUIRED IMPORTED_TARGET dep)
+    """)
+    client.save({
+        "dep/conanfile.py": dep,
+        "pkg/conanfile.py": pkg,
+        "pkg/CMakeLists.txt": cmakelists
+    })
+    client.run("create dep/conanfile.py")
+    client.run("create pkg/conanfile.py")
+    assert "Found dep, version 1.0" in client.out
diff --git a/conans/test/functional/toolchains/gnu/autotools/test_win_bash.py b/conans/test/functional/toolchains/gnu/autotools/test_win_bash.py
index 03da7e627..1fbb389a8 100644
--- a/conans/test/functional/toolchains/gnu/autotools/test_win_bash.py
+++ b/conans/test/functional/toolchains/gnu/autotools/test_win_bash.py
@@ -66,3 +66,41 @@ def test_autotools_bash_complete():
     # To check that the ``autotools.install()`` has worked correctly
     # FIXME IN CONAN 2.0 this will break, no local `package_folder`
     assert os.path.exists(os.path.join(client.current_folder, "package", "bin", "main.exe"))
+
+
+@pytest.mark.skipif(platform.system() != "Windows", reason="Requires Windows")
+def test_add_msys2_path_automatically():
+    """ Check that commands like ar, autoconf, etc, that are in the /usr/bin folder together
+    with the bash.exe, can be automaticallly used when running in windows bash, without user
+    extra addition to [buildenv] of that msys64/usr/bin path
+
+    # https://github.com/conan-io/conan/issues/12110
+    """
+    client = TestClient(path_with_spaces=False)
+    bash_path = None
+    try:
+        bash_path = tools_locations["msys2"]["system"]["path"]["Windows"] + "/bash.exe"
+    except KeyError:
+        pytest.skip("msys2 path not defined")
+
+    save(client.cache.new_config_path, textwrap.dedent("""
+            tools.microsoft.bash:subsystem=msys2
+            tools.microsoft.bash:path={}
+            """.format(bash_path)))
+
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+
+        class HelloConan(ConanFile):
+            name = "hello"
+            version = "0.1"
+
+            win_bash = True
+
+            def build(self):
+                self.run("ar -h")
+                """)
+
+    client.save({"conanfile.py": conanfile})
+    client.run("create .")
+    assert "ar.exe" in client.out
diff --git a/conans/test/functional/toolchains/test_nmake_toolchain.py b/conans/test/functional/toolchains/test_nmake_toolchain.py
new file mode 100644
index 000000000..532ad34b3
--- /dev/null
+++ b/conans/test/functional/toolchains/test_nmake_toolchain.py
@@ -0,0 +1,51 @@
+import platform
+import textwrap
+
+import pytest
+
+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
+
+
+@pytest.mark.parametrize("compiler, version, runtime, cppstd, build_type",
+                         [("msvc", "190", "dynamic", "14", "Release"),
+                          ("msvc", "191", "static", "17", "Debug")])
+@pytest.mark.skipif(platform.system() != "Windows", reason="Only for windows")
+def test_toolchain_nmake(compiler, version, runtime, cppstd, build_type):
+    client = TestClient(path_with_spaces=False)
+    settings = {"compiler": compiler,
+                "compiler.version": version,
+                "compiler.cppstd": cppstd,
+                "compiler.runtime": runtime,
+                "build_type": build_type,
+                "arch": "x86_64"}
+
+    # Build the profile according to the settings provided
+    settings = " ".join('-s %s="%s"' % (k, v) for k, v in settings.items() if v)
+
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        class Pkg(ConanFile):
+            settings = "os", "compiler", "build_type", "arch"
+            generators = "NMakeToolchain"
+
+            def build(self):
+                self.run(f"nmake /f makefile")
+            """)
+    makefile = textwrap.dedent("""\
+        all: simple.exe
+
+        .cpp.obj:
+          cl $(cppflags) $*.cpp
+
+        simple.exe: simple.obj
+          cl $(cppflags) simple.obj
+        """)
+    client.save({"conanfile.py": conanfile,
+                 "makefile": makefile,
+                 "simple.cpp": gen_function_cpp(name="main")})
+    client.run("install . {}".format(settings))
+    client.run("build .")
+    client.run_command("simple.exe")
+    check_exe_run(client.out, "main", "msvc", version, build_type, "x86_64", cppstd)
diff --git a/conans/test/functional/utils.py b/conans/test/functional/utils.py
index e318c6c6d..611fb8bab 100644
--- a/conans/test/functional/utils.py
+++ b/conans/test/functional/utils.py
@@ -83,7 +83,7 @@ def check_exe_run(output, names, compiler, version, build_type, arch, cppstd, de
         if extra_msg:  # For ``conan new`` templates
             assert "{} {} {}".format(name, extra_msg, build_type) in output
         else:
-            assert "{}: {}".format(name, extra_msg, build_type) in output
+            assert "{}: {}".format(name, build_type) in output
         if compiler == "msvc":
             if arch == "x86":
                 assert "{} _M_IX86 defined".format(name) in output
diff --git a/conans/test/integration/layout/test_layout_paths.py b/conans/test/integration/layout/test_layout_paths.py
new file mode 100644
index 000000000..5d863f411
--- /dev/null
+++ b/conans/test/integration/layout/test_layout_paths.py
@@ -0,0 +1,33 @@
+import textwrap
+
+from conans.test.assets.genconanfile import GenConanfile
+from conans.test.utils.tools import TestClient
+
+
+def test_editable_layout_paths():
+    # https://github.com/conan-io/conan/issues/12521
+    c = TestClient()
+    dep = textwrap.dedent("""
+        import os
+        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"]
+            """)
+    c.save({"dep/conanfile.py": dep,
+            "pkg/conanfile.py": GenConanfile("pkg", "0.1").with_settings("build_type", "arch")
+                                                          .with_requires("dep/0.1")
+                                                          .with_generator("CMakeDeps")
+                                                          .with_generator("PkgConfigDeps")})
+    c.run("editable add dep dep/0.1")
+    c.run("install pkg")
+    # It doesn't crash anymore
+    assert "dep/0.1:5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9 - Editable" in c.out
+    arch = c.get_default_host_profile().settings['arch']
+    data = c.load(f"dep-release-{arch}-data.cmake")
+    assert 'set(dep_INCLUDE_DIRS_RELEASE "${dep_PACKAGE_FOLDER_RELEASE}/include")' in data
+    pc = c.load("dep.pc")
+    assert "includedir1=${prefix}/include" in pc
diff --git a/conans/test/integration/package_id/test_validate.py b/conans/test/integration/package_id/test_validate.py
index e77a007ce..eeb1268d3 100644
--- a/conans/test/integration/package_id/test_validate.py
+++ b/conans/test/integration/package_id/test_validate.py
@@ -353,3 +353,55 @@ class TestValidate(unittest.TestCase):
         assert "conanfile.py: ERROR: Invalid ID: never ever" in c.out
         assert "Trying to install dependencies, but this configuration will fail to build a package"\
                in c.out
+
+
+class TestValidateCppstd:
+    """ aims to be a very close to real use case of cppstd management and validation in recipes
+    """
+    def test_build_17_consume_14(self):
+        client = TestClient()
+        conanfile = textwrap.dedent("""
+            from conan import ConanFile
+            from conan.errors import ConanInvalidConfiguration
+            class Pkg(ConanFile):
+                name = "pkg"
+                version = "0.1"
+                settings = "compiler"
+
+                def validate_build(self):
+                    # Explicit logic instead of using check_min_cppstd that hides details
+                    if int(str(self.settings.compiler.cppstd)) < 17:
+                        raise ConanInvalidConfiguration("I need at least cppstd=17 to build")
+
+                def validate(self):
+                    if int(str(self.settings.compiler.cppstd)) < 14:
+                        raise ConanInvalidConfiguration("I need at least cppstd=14 to be used")
+
+                def compatibility(conanfile):
+                    return [{"settings": [("compiler.cppstd", v)]} for v in ("11", "14", "17", "20")]
+            """)
+
+        client.save({"conanfile.py": conanfile})
+
+        settings = "-s compiler=gcc -s compiler.version=9 -s compiler.libcxx=libstdc++"
+        client.run(f"create . {settings} -s compiler.cppstd=17")
+        assert "pkg/0.1:7558a15eea8c5762fa0602874a38dde9d36229c1 - Build" in client.out
+
+        # create with cppstd=14 fails, not enough
+        client.run(f"create . {settings} -s compiler.cppstd=14", assert_error=True)
+        assert "pkg/0.1:e480cd97ef8f9dfdc31a7c0710c26d9b8d78375a - Invalid" in client.out
+        assert "pkg/0.1: Cannot build for this configuration: I need at least cppstd=17 to build" \
+               in client.out
+
+        # Install with cppstd=14 can fallback to the previous one
+        client.run(f"install pkg/0.1@ {settings} -s compiler.cppstd=14")
+        assert "Using compatible package '7558a15eea8c5762fa0602874a38dde9d36229c1'" in client.out
+        assert "pkg/0.1:7558a15eea8c5762fa0602874a38dde9d36229c1 - Cache" in client.out
+
+        # FIXME 2.0: install with not enough cppstd should fail, but it doesnt
+        # It is important to know that this only happens with "compatible_packages", otherwise
+        # it will also fail (ConanCenter)
+        client.run(f"install pkg/0.1@ {settings} -s compiler.cppstd=11")
+        # not even trying to fallback to compatibles
+        assert "Using compatible package '7558a15eea8c5762fa0602874a38dde9d36229c1'" in client.out
+        assert "pkg/0.1:7558a15eea8c5762fa0602874a38dde9d36229c1 - Cache" in client.out
diff --git a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
index d0bef4d84..1b1a9b76c 100644
--- a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -758,3 +758,29 @@ def test_presets_ninja_msvc(arch, arch_toolset):
     presets = json.loads(client.load("build/14/generators/CMakePresets.json"))
     assert "architecture" in presets["configurePresets"][0]
     assert "toolset" not in presets["configurePresets"][0]
+
+
+def test_pkg_config_block():
+    os_ = platform.system()
+    os_ = "Macos" if os_ == "Darwin" else os_
+    profile = textwrap.dedent("""
+        [settings]
+        os=%s
+        arch=x86_64
+
+        [conf]
+        tools.gnu:pkg_config=/usr/local/bin/pkg-config
+        """ % os_)
+
+    client = TestClient(path_with_spaces=False)
+    conanfile = GenConanfile().with_settings("os", "arch")\
+        .with_generator("CMakeToolchain")
+    client.save({"conanfile.py": conanfile,
+                "profile": profile})
+    client.run("install . -pr:b profile -pr:h profile")
+    toolchain = client.load("conan_toolchain.cmake")
+    assert 'set(PKG_CONFIG_EXECUTABLE /usr/local/bin/pkg-config CACHE FILEPATH ' in toolchain
+    pathsep = ":" if os_ != "Windows" else ";"
+    pkg_config_path_set = 'set(ENV{PKG_CONFIG_PATH} "%s$ENV{PKG_CONFIG_PATH}")' % \
+                          (client.current_folder.replace("\\", "/") + pathsep)
+    assert pkg_config_path_set in toolchain
diff --git a/conans/test/integration/toolchains/google/test_bazel.py b/conans/test/integration/toolchains/google/test_bazel.py
index 6140881ff..aab061406 100644
--- a/conans/test/integration/toolchains/google/test_bazel.py
+++ b/conans/test/integration/toolchains/google/test_bazel.py
@@ -76,3 +76,48 @@ def test_bazel_exclude_folders():
     c.run("install dep/0.1@ -g BazelDeps")
     build_file = c.load("dep/BUILD")
     assert 'static_library = "lib/libmymath.a"' in build_file
+
+
+def test_bazeldeps_and_build_requires():
+    """
+    Testing that direct build requires are not included in dependencies BUILD files
+
+    Issues related:
+        * https://github.com/conan-io/conan/issues/12444
+        * https://github.com/conan-io/conan/issues/12236
+    """
+    c = TestClient()
+    tool = textwrap.dedent("""
+        import os
+        from conan import ConanFile
+
+        class ToolConanfile(ConanFile):
+            name = "tool"
+            version = "0.1"
+
+            def package_info(self):
+                self.cpp_info.libs = ["mymath"]
+        """)
+    c.save({"tool/conanfile.py": tool})
+    c.run("create tool")
+    dep = textwrap.dedent("""
+        import os
+        from conan import ConanFile
+        from conan.tools.files import save
+        class ExampleConanIntegration(ConanFile):
+            name = "dep"
+            version = "0.1"
+
+            def build_requirements(self):
+                self.build_requires("tool/0.1")
+            def package(self):
+                save(self, os.path.join(self.package_folder, "lib", "mymath", "otherfile.a"), "")
+                save(self, os.path.join(self.package_folder, "lib", "libmymath.a"), "")
+            def package_info(self):
+                self.cpp_info.libs = ["mymath"]
+        """)
+    c.save({"dep/conanfile.py": dep})
+    c.run("export dep")
+    c.run("install dep/0.1@ -g BazelDeps --build=missing")
+    build_file = c.load("dep/BUILD")
+    assert '@tool' not in build_file
diff --git a/conans/test/unittests/client/toolchain/autotools/autotools_test.py b/conans/test/unittests/client/toolchain/autotools/autotools_test.py
index 50bcf91a4..c310469bd 100644
--- a/conans/test/unittests/client/toolchain/autotools/autotools_test.py
+++ b/conans/test/unittests/client/toolchain/autotools/autotools_test.py
@@ -32,12 +32,47 @@ def test_configure_arguments():
 
     ab = Autotools(conanfile)
     ab.make()
-    assert "my_make my_make_args -j23" in runner.command_called
+    assert "my_make my_make_args -j23" == runner.command_called
 
     # test install target argument
 
     ab.install()
-    assert 'my_make install my_make_args DESTDIR=None -j23' in runner.command_called
+    assert 'my_make install my_make_args DESTDIR=None -j23' == runner.command_called
 
     ab.install(target="install_other")
-    assert 'my_make install_other my_make_args DESTDIR=None -j23' in runner.command_called
+    assert 'my_make install_other my_make_args DESTDIR=None -j23' == runner.command_called
+
+    for make_args in ["my_make_args", ""]:
+
+        save_toolchain_args({
+            "configure_args": "my_configure_args",
+            "make_args": f"{make_args}"}
+        )
+
+        ab = Autotools(conanfile)
+
+        make_args_str = f" {make_args}" if make_args else ""
+
+        ab.make(args=["-j1"])
+        assert "-j23" not in runner.command_called
+        assert f"my_make{make_args_str} -j1" == runner.command_called
+
+        ab.install(args=["-j1"])
+        assert "-j23" not in runner.command_called
+        assert f"my_make install{make_args_str} DESTDIR=None -j1" == runner.command_called
+
+        ab.install(args=["DESTDIR=whatever", "-j1"])
+        assert "-j23" not in runner.command_called
+        assert f"my_make install{make_args_str} DESTDIR=whatever -j1" == runner.command_called
+
+        ab.install(args=["DESTDIR=whatever", "-arg1 -j1 -arg2"])
+        assert "-j23" not in runner.command_called
+        assert f"my_make install{make_args_str} DESTDIR=whatever -arg1 -j1 -arg2" == runner.command_called
+
+        # check that we don't detect -j in an argument as number of jobs
+        ab.install(args=["DESTDIR=/user/smith-john/whatever"])
+        assert f"my_make install{make_args_str} DESTDIR=/user/smith-john/whatever -j23" == runner.command_called
+
+        # check that we don't detect -j in an argument as number of jobs
+        ab.install(args=["DESTDIR=/user/smith-j47/whatever"])
+        assert f"my_make install{make_args_str} DESTDIR=/user/smith-j47/whatever -j23" == runner.command_called
diff --git a/conans/test/unittests/tools/env/test_env.py b/conans/test/unittests/tools/env/test_env.py
index 0545fd8ed..fb40a69be 100644
--- a/conans/test/unittests/tools/env/test_env.py
+++ b/conans/test/unittests/tools/env/test_env.py
@@ -410,3 +410,14 @@ class TestProfileEnvRoundTrip:
             MyPath1=+(path)/my/path12
             MyPath1+=(path)/my/path11
             """)
+
+
+def test_custom_placeholder():
+    # https://github.com/conan-io/conan/issues/12427
+    env = Environment()
+    env.append_path("MyVar", "MyValue")
+    env = env.vars(ConanFileMock())
+    assert env.get("MyVar", variable_reference="$penv{{{name}}}") == \
+           f"$penv{{MyVar}}{os.pathsep}MyValue"
+    items = {k: v for k, v in env.items(variable_reference="$penv{{{name}}}")}
+    assert  items == {"MyVar": f"$penv{{MyVar}}{os.pathsep}MyValue"}