Repository: conan-io/conan
Pull request #12291 (2022-10-12 10:51:27)

Add ability to set custom host triplet passed by Autotools toolchain
Changelog: Feature:  Add `tools.gnu:host_triplet` conf setting to customise the host triplet set by the AutoTools toolchain
Docs:  TODO

This setting can be useful in the following situations:

- When the user is using a combination of `os`/`arch` for which Conan does not know how to construct the GNU host triplet. In this situation, an exception would be thrown upon constructing the `AutotoolsToolchain()` toolchain object, leaving the user without much recourse.  This can happen if the user is using custom architectures in `settings.yml`, or if Conan doesn't know which triplet to use for the specific `os`/`arch` combination.
- When Conan _can_ produce a valid triplet based on `os`/`arch`, but the specific target platform has a different convention. E.g. for some embedded devices one may want to specify the CPU in a more comprehensive way, or actually specify the vendor (rather than the implicit `unknown`).

In a way, this is analogous to being able to define `system_name`, `system_version`, `system_processor` via the `tools.cmake.cmaketoolchain:` conf settings.

Close: https://github.com/conan-io/conan/issues/12217


Patch:
diff --git a/conan/tools/env/virtualbuildenv.py b/conan/tools/env/virtualbuildenv.py
index edb1c6777..35be4f311 100644
--- a/conan/tools/env/virtualbuildenv.py
+++ b/conan/tools/env/virtualbuildenv.py
@@ -23,9 +23,9 @@ class VirtualBuildEnv:
     def _filename(self):
         f = self.basename
         if self.configuration:
-            f += "-" + self.configuration
+            f += "-" + self.configuration.replace(".", "_")
         if self.arch:
-            f += "-" + self.arch
+            f += "-" + self.arch.replace(".", "_")
         return f
 
     def environment(self):
diff --git a/conan/tools/env/virtualrunenv.py b/conan/tools/env/virtualrunenv.py
index 529aadbda..4a22da6e8 100644
--- a/conan/tools/env/virtualrunenv.py
+++ b/conan/tools/env/virtualrunenv.py
@@ -45,9 +45,9 @@ class VirtualRunEnv:
     def _filename(self):
         f = self.basename
         if self.configuration:
-            f += "-" + self.configuration
+            f += "-" + self.configuration.replace(".", "_")
         if self.arch:
-            f += "-" + self.arch
+            f += "-" + self.arch.replace(".", "_")
         return f
 
     def environment(self):
diff --git a/conan/tools/gnu/autotoolstoolchain.py b/conan/tools/gnu/autotoolstoolchain.py
index 7269fa2f7..7b0f735df 100644
--- a/conan/tools/gnu/autotoolstoolchain.py
+++ b/conan/tools/gnu/autotoolstoolchain.py
@@ -43,8 +43,8 @@ class AutotoolsToolchain:
         self.fpic = self._conanfile.options.get_safe("fPIC")
         self.msvc_runtime_flag = self._get_msvc_runtime_flag()
 
-        # Cross build
-        self._host = None
+        # Cross build triplets
+        self._host = self._conanfile.conf.get("tools.gnu:host_triplet")
         self._build = None
         self._target = None
 
@@ -56,7 +56,8 @@ class AutotoolsToolchain:
         if cross_building(self._conanfile):
             os_build, arch_build, os_host, arch_host = get_cross_building_settings(self._conanfile)
             compiler = self._conanfile.settings.get_safe("compiler")
-            self._host = _get_gnu_triplet(os_host, arch_host, compiler=compiler)
+            if not self._host:
+                self._host = _get_gnu_triplet(os_host, arch_host, compiler=compiler)
             self._build = _get_gnu_triplet(os_build, arch_build, compiler=compiler)
 
             # Apple Stuff
diff --git a/conan/tools/microsoft/msbuilddeps.py b/conan/tools/microsoft/msbuilddeps.py
index 52353f433..3c3d2739c 100644
--- a/conan/tools/microsoft/msbuilddeps.py
+++ b/conan/tools/microsoft/msbuilddeps.py
@@ -25,7 +25,6 @@ class MSBuildDeps(object):
           <PropertyGroup Label="ConanVariables">
             <Conan{{name}}RootFolder>{{root_folder}}</Conan{{name}}RootFolder>
             <Conan{{name}}BinaryDirectories>{{bin_dirs}}</Conan{{name}}BinaryDirectories>
-            <Conan{{name}}Dependencies>{{dependencies}}</Conan{{name}}Dependencies>
             {% if host_context %}
             <Conan{{name}}CompilerFlags>{{compiler_flags}}</Conan{{name}}CompilerFlags>
             <Conan{{name}}LinkerFlags>{{linker_flags}}</Conan{{name}}LinkerFlags>
@@ -134,7 +133,7 @@ class MSBuildDeps(object):
     def _get_valid_xml_format(name):
         return re.compile(r"[.+]").sub("_", name)
 
-    def _vars_props_file(self, dep, name, cpp_info, deps, build):
+    def _vars_props_file(self, dep, name, cpp_info, build):
         """
         content for conan_vars_poco_x86_release.props, containing the variables for 1 config
         This will be for 1 package or for one component of a package
@@ -180,7 +179,6 @@ class MSBuildDeps(object):
             'definitions': "".join("%s;" % d for d in cpp_info.defines),
             'compiler_flags': " ".join(cpp_info.cxxflags + cpp_info.cflags),
             'linker_flags': " ".join(cpp_info.sharedlinkflags + cpp_info.exelinkflags),
-            'dependencies': ";".join(deps),
             'host_context': not build
         }
         formatted_template = Template(self._vars_props, trim_blocks=True,
@@ -291,15 +289,16 @@ class MSBuildDeps(object):
                 comp_filename = "conan_%s.props" % full_comp_name
                 pkg_filename = "conan_%s.props" % dep_name
 
-                public_deps = []
+                public_deps = []  # To store the xml dependencies/file names
                 for r in comp_info.requires:
                     if "::" in r:  # Points to a component of a different package
                         pkg, cmp_name = r.split("::")
                         public_deps.append(pkg if pkg == cmp_name else "{}_{}".format(pkg, cmp_name))
                     else:  # Points to a component of same package
                         public_deps.append("{}_{}".format(dep_name, r))
+                public_deps = [self._get_valid_xml_format(d) for d in public_deps]
                 result[vars_filename] = self._vars_props_file(dep, full_comp_name, comp_info,
-                                                              public_deps, build=build)
+                                                              build=build)
                 result[activate_filename] = self._activate_props_file(full_comp_name, vars_filename,
                                                                       public_deps, build=build)
                 result[comp_filename] = self._dep_props_file(full_comp_name, comp_filename,
@@ -317,7 +316,7 @@ class MSBuildDeps(object):
             public_deps = [self._dep_name(d, build)
                            for r, d in dep.dependencies.direct_host.items() if r.visible]
             result[vars_filename] = self._vars_props_file(dep, dep_name, cpp_info,
-                                                          public_deps, build=build)
+                                                          build=build)
             result[activate_filename] = self._activate_props_file(dep_name, vars_filename,
                                                                   public_deps, build=build)
             result[pkg_filename] = self._dep_props_file(dep_name, pkg_filename, activate_filename,
diff --git a/conan/tools/scm/git.py b/conan/tools/scm/git.py
index 4a5c8194e..1032d5b69 100644
--- a/conan/tools/scm/git.py
+++ b/conan/tools/scm/git.py
@@ -87,3 +87,8 @@ class Git(object):
     def checkout(self, commit):
         self._conanfile.output.info("Checkout: {}".format(commit))
         self.run('checkout {}'.format(commit))
+
+    def included_files(self):
+        files = self.run("ls-files --full-name --others --cached --exclude-standard")
+        files = files.splitlines()
+        return files
diff --git a/conans/client/cache/cache.py b/conans/client/cache/cache.py
index a35d2a406..b827edb16 100644
--- a/conans/client/cache/cache.py
+++ b/conans/client/cache/cache.py
@@ -170,7 +170,10 @@ class ClientCache(object):
             self._new_config = ConfDefinition()
             if os.path.exists(self.new_config_path):
                 text = load(self.new_config_path)
-                content = Template(text).render({"platform": platform, "os": os})
+                distro = None
+                if platform.system() in ["Linux", "FreeBSD"]:
+                    import distro
+                content = Template(text).render({"platform": platform, "os": os, "distro": distro})
                 self._new_config.loads(content)
         return self._new_config
 
diff --git a/conans/client/command.py b/conans/client/command.py
index 9c1469ac8..5b548b805 100644
--- a/conans/client/command.py
+++ b/conans/client/command.py
@@ -1575,7 +1575,7 @@ class Command(object):
         parser_add.add_argument('remote', help='Name of the remote')
         parser_add.add_argument('url', help='URL of the remote')
         parser_add.add_argument('verify_ssl', nargs="?", default="True",
-                                help='Verify SSL certificated. Default True')
+                                help='Verify SSL certificate. Defaulted to True')
         parser_add.add_argument("-i", "--insert", nargs="?", const=0, type=int, action=OnceArgument,
                                 help="insert remote at specific index")
         parser_add.add_argument("-f", "--force", default=False, action='store_true',
@@ -1587,7 +1587,7 @@ class Command(object):
 
         parser_upd.add_argument('url', help='URL')
         parser_upd.add_argument('verify_ssl', nargs="?", default="True",
-                                help='Verify SSL certificated. Default True')
+                                help='Verify SSL certificate. Defaulted to True')
         parser_upd.add_argument("-i", "--insert", nargs="?", const=0, type=int, action=OnceArgument,
                                 help="Insert remote at specific index")
         parser_rename = subparsers.add_parser('rename', help='Update the remote name')
diff --git a/conans/model/conf.py b/conans/model/conf.py
index 4df28790e..b7044625b 100644
--- a/conans/model/conf.py
+++ b/conans/model/conf.py
@@ -28,6 +28,7 @@ BUILT_IN_CONFS = {
     "tools.files.download:retry_wait": "Seconds to wait between download attempts",
     "tools.gnu:make_program": "Indicate path to make program",
     "tools.gnu:define_libcxx11_abi": "Force definition of GLIBCXX_USE_CXX11_ABI=1 for libstdc++11",
+    "tools.gnu:host_triplet": "Custom host triplet to pass to Autotools scripts",
     "tools.google.bazel:configs": "Define Bazel config file",
     "tools.google.bazel:bazelrc_path": "Defines Bazel rc-path",
     "tools.microsoft.msbuild:verbosity": "Verbosity level for MSBuild: 'Quiet', 'Minimal', 'Normal', 'Detailed', 'Diagnostic'",


Test patch:
diff --git a/conans/test/functional/toolchains/gnu/autotools/test_crossbuild_triplet.py b/conans/test/functional/toolchains/gnu/autotools/test_crossbuild_triplet.py
new file mode 100644
index 000000000..d379b17b4
--- /dev/null
+++ b/conans/test/functional/toolchains/gnu/autotools/test_crossbuild_triplet.py
@@ -0,0 +1,53 @@
+import platform
+import pytest
+import textwrap
+
+from conans.test.utils.tools import TestClient
+
+@pytest.mark.skipif(platform.system() not in  ["Darwin", "Linux"], reason="Autotools on Linux or macOS")
+def test_crossbuild_triplet_from_conf():
+
+    settings_yml = textwrap.dedent("""
+        os:
+            Linux:
+            Windows:
+        arch: [x86_64, hexagon]
+        compiler:
+            gcc:
+                version: ["10", "11"]
+                libcxx: [libstdc++11]
+        build_type: [None, Debug, Release]
+        """)
+    
+    host_profile = textwrap.dedent("""
+        [settings]
+        os=Linux
+        arch=hexagon
+        compiler=gcc
+        compiler.version=10
+        compiler.libcxx=libstdc++11
+        build_type=Release
+        [conf]
+        tools.gnu:host_triplet=hexagon-acme-linux-gnu
+    """)
+
+    build_profile = textwrap.dedent("""
+        [settings]
+        os=Linux
+        arch=x86_64
+        compiler=gcc
+        compiler.version=11
+        compiler.libcxx=libstdc++11
+        build_type=Release   
+    """)
+
+    client = TestClient(path_with_spaces=False)
+    client.save({client.cache.settings_path: settings_yml})
+    client.save({"host_profile": host_profile})
+    client.save({"build_profile": build_profile})
+
+    client.run("new hello/0.1 --template=autotools_lib")
+    client.run("create . --profile:build=build_profile --profile:host=host_profile -tf None")
+
+    assert "--host=hexagon-acme-linux-gnu" in client.out
+    assert "checking host system type... hexagon-acme-linux-gnu" in client.out
diff --git a/conans/test/functional/toolchains/microsoft/test_msbuilddeps.py b/conans/test/functional/toolchains/microsoft/test_msbuilddeps.py
index 9c6a9120b..f36d3bede 100644
--- a/conans/test/functional/toolchains/microsoft/test_msbuilddeps.py
+++ b/conans/test/functional/toolchains/microsoft/test_msbuilddeps.py
@@ -1008,4 +1008,4 @@ def test_build_requires_transitives():
     assert "conan_dep_build.props" in tool
     assert "conan_dep.props" not in tool
     tool_vars = c.load("conan_tool_build_vars_release_x64.props")
-    assert "<Conantool_buildDependencies>dep_build</Conantool_buildDependencies>" in tool_vars
+    assert "<Conantool_buildRootFolder>" in tool_vars
diff --git a/conans/test/functional/tools/scm/test_git.py b/conans/test/functional/tools/scm/test_git.py
index cd1e47c06..499319541 100644
--- a/conans/test/functional/tools/scm/test_git.py
+++ b/conans/test/functional/tools/scm/test_git.py
@@ -585,3 +585,72 @@ class TestConanFileSubfolder:
         c.init_git_repo()
         c.run("export conan")
         assert "pkg/0.1: git version" in c.out
+
+
+class TestGitIncluded:
+    def test_git_included(self):
+        conanfile = textwrap.dedent("""
+            import os
+            import shutil
+            from conan import ConanFile
+            from conan.tools.scm import Git
+
+            class Pkg(ConanFile):
+                name = "pkg"
+                version = "0.1"
+
+                def export_sources(self):
+                    git = Git(self)
+                    included = git.included_files()
+                    for i in included:
+                        dst =  os.path.join(self.export_sources_folder, i)
+                        os.makedirs(os.path.dirname(dst), exist_ok=True)
+                        print("COPYING ", i, dst)
+                        shutil.copy2(i, dst)
+
+                def source(self):
+                    self.output.info("SOURCES: {}!!".format(sorted(os.listdir("."))))
+                    self.output.info("SOURCES_SUB: {}!!".format(sorted(os.listdir("sub"))))
+            """)
+
+        c = TestClient()
+        c.save({"conanfile.py": conanfile,
+                ".gitignore": "*.txt",
+                "myfile.txt": "test",
+                "myfile.other": "othertest",
+                "sub/otherfile": "other"})
+        c.init_git_repo()
+        c.run("create .")
+        assert "pkg/0.1: SOURCES: ['.gitignore', 'myfile.other', 'sub']!!" in c.out
+        assert "pkg/0.1: SOURCES_SUB: ['otherfile']!!" in c.out
+
+    def test_git_included_subfolder(self):
+        conanfile = textwrap.dedent("""
+            import os
+            import shutil
+            from conan import ConanFile
+            from conan.tools.scm import Git
+
+            class Pkg(ConanFile):
+                name = "pkg"
+                version = "0.1"
+
+                def export_sources(self):
+                    git = Git(self, "src")
+                    included = git.included_files()
+                    for i in included:
+                        shutil.copy2(i, self.export_sources_folder)
+
+                def source(self):
+                    self.output.info("SOURCES: {}!!".format(sorted(os.listdir("."))))
+            """)
+
+        c = TestClient()
+        c.save({"conanfile.py": conanfile,
+                ".gitignore": "*.txt",
+                "somefile": "some",
+                "src/myfile.txt": "test",
+                "src/myfile.other": "othertest"})
+        c.init_git_repo()
+        c.run("create .")
+        assert "pkg/0.1: SOURCES: ['myfile.other']!!" in c.out
diff --git a/conans/test/integration/configuration/conf/test_conf.py b/conans/test/integration/configuration/conf/test_conf.py
index 5bdeb5477..9310a156b 100644
--- a/conans/test/integration/configuration/conf/test_conf.py
+++ b/conans/test/integration/configuration/conf/test_conf.py
@@ -177,10 +177,15 @@ def test_composition_conan_conf_different_data_types_by_cli_arg(client):
     assert "tools.microsoft.msbuildtoolchain:compile_options${'ExceptionHandling': 'Async'}" in client.out
 
 
-@pytest.mark.skipif(six.PY2, reason="only Py3")
 def test_jinja_global_conf(client):
     save(client.cache.new_config_path, "user.mycompany:parallel = {{os.cpu_count()/2}}\n"
-                                       "user.mycompany:other = {{platform.system()}}\n")
+                                       "user.mycompany:other = {{platform.system()}}\n"
+                                       "user.mycompany:dist = {{distro.id() if distro else '42'}}\n")
     client.run("install .")
     assert "user.mycompany:parallel={}".format(os.cpu_count()/2) in client.out
     assert "user.mycompany:other={}".format(platform.system()) in client.out
+    if platform.system() == "Linux":
+        import distro
+        assert "user.mycompany:dist={}".format(distro.id()) in client.out
+    else:
+        assert "user.mycompany:dist=42" in client.out
diff --git a/conans/test/integration/environment/test_env.py b/conans/test/integration/environment/test_env.py
index f60c0c4db..d81f55ef7 100644
--- a/conans/test/integration/environment/test_env.py
+++ b/conans/test/integration/environment/test_env.py
@@ -681,3 +681,41 @@ def test_files_always_created():
     assert os.path.isfile(os.path.join(c.current_folder, f"conanrun.{ext}"))
     assert os.path.isfile(os.path.join(c.current_folder, f"conanbuildenv-release-{arch}.{ext}"))
     assert os.path.isfile(os.path.join(c.current_folder, f"conanbuildenv-release-{arch}.{ext}"))
+
+
+def test_error_with_dots_virtualenv():
+    # https://github.com/conan-io/conan/issues/12163
+    tool = textwrap.dedent(r"""
+        from conan import ConanFile
+        class ToolConan(ConanFile):
+            name = "tool"
+            version = "0.1"
+            settings = "arch", "os"
+
+            def package_info(self):
+                self.buildenv_info.define("DUMMY", "123456")
+        """)
+    test_package = textwrap.dedent(r"""
+        from conan import ConanFile
+        import os
+
+        class ToolTestConan(ConanFile):
+            name = "app"
+            version = "0.1"
+            settings = "os", "compiler", "build_type", "arch"
+            generators = "VirtualBuildEnv"
+
+            def build_requirements(self):
+                self.tool_requires("tool/0.1")
+
+            def build(self):
+                self.run("echo DUMMY=$DUMMY")
+                self.run("set")
+            """)
+    client = TestClient()
+    client.save({"dep/conanfile.py": tool,
+                 "consumer/conanfile.py": test_package})
+
+    client.run("create dep -s arch=armv8.3")
+    client.run("create consumer -s arch=armv8.3")
+    assert "DUMMY=123456" in client.out
diff --git a/conans/test/integration/toolchains/microsoft/test_msbuilddeps.py b/conans/test/integration/toolchains/microsoft/test_msbuilddeps.py
index 0422276c3..32f40c7c2 100644
--- a/conans/test/integration/toolchains/microsoft/test_msbuilddeps.py
+++ b/conans/test/integration/toolchains/microsoft/test_msbuilddeps.py
@@ -45,6 +45,19 @@ def test_msbuilddeps_format_names():
     c.run("install pkg.name-more+/1.0@ -g MSBuildDeps -s build_type=Release -s arch=x86_64")
     # Checking that MSBuildDeps builds correctly the XML file
     # loading all .props and xml parse them to check no errors
+    pkg_more = c.load("conan_pkg_name-more_.props")
+    assert "$(conan_pkg_name-more__libmpdecimal___props_imported)" in pkg_more
+    assert "$(conan_pkg_name-more__mycomp_some-comp__props_imported)" in pkg_more
+
+    some_comp = c.load("conan_pkg_name-more__mycomp_some-comp_.props")
+    assert "<conan_pkg_name-more__mycomp_some-comp__props_imported>" in some_comp
+
+    libmpdecimal = c.load("conan_pkg_name-more__libmpdecimal__.props")
+    assert "<conan_pkg_name-more__libmpdecimal___props_imported>" in libmpdecimal
+
+    libmpdecimal_release = c.load("conan_pkg_name-more__libmpdecimal___release_x64.props")
+    assert "$(conan_pkg_name-more__mycomp_some-comp__props_imported)" in libmpdecimal_release
+
     counter = 0
     for f in os.listdir(c.current_folder):
         if f.endswith(".props"):
diff --git a/conans/test/unittests/client/toolchain/autotools/autotools_toolchain_test.py b/conans/test/unittests/client/toolchain/autotools/autotools_toolchain_test.py
index acbe860bb..aedd2fdf9 100644
--- a/conans/test/unittests/client/toolchain/autotools/autotools_toolchain_test.py
+++ b/conans/test/unittests/client/toolchain/autotools/autotools_toolchain_test.py
@@ -58,6 +58,15 @@ def test_invalid_target_triple():
     assert "Unknown 'UNKNOWN_ARCH' machine, Conan doesn't know how " \
            "to translate it to the GNU triplet," in str(excinfo)
 
+def test_custom_host_triple():
+    conanfile = ConanFileMock()
+    conanfile.settings = MockSettings({"os": "Linux", "arch": "x86"})
+    conanfile.settings_build = MockSettings({"os": "Linux", "arch": "x86_64"})
+    conanfile.conf.define("tools.gnu:host_triplet", "i686-pc-linux-gnu")
+    tc = AutotoolsToolchain(conanfile)
+    tc.generate_args()
+    obj = load_toolchain_args()
+    assert "--host=i686-pc-linux-gnu" in obj["configure_args"]
 
 def test_cppstd():
     # Using "cppstd" is discarded