Repository: conan-io/conan

Issue:
[build system] [MSBuild] MSBuild() can't receive custom properties
For example, if I want to invoke MSBuild with `/p:MyCustomProperty=true` I can't do it. (If you try to pass it in the project file, it invokes the project update with the property and fails)
To study if an "args" parameter is missing to build or a "properties" that manages the `/p:` for us (or both)?



Discussion:

Patch:
diff --git a/conans/client/build/cmake.py b/conans/client/build/cmake.py
index f70eba2ea..5f1b60dd4 100644
--- a/conans/client/build/cmake.py
+++ b/conans/client/build/cmake.py
@@ -17,7 +17,7 @@ from conans.tools import cpu_count, args_to_string
 from conans import tools
 from conans.util.log import logger
 from conans.util.config_parser import get_bool_from_text
-from conans.client.build.compiler_flags import architecture_flag
+from conans.client.build.compiler_flags import architecture_flag, parallel_compiler_cl_flag
 
 
 def _get_env_cmake_system_name():
@@ -325,9 +325,9 @@ class CMake(object):
 
         if str(self._os) in ["Windows", "WindowsStore"] and self._compiler == "Visual Studio":
             if self.parallel:
-                cpus = tools.cpu_count()
-                ret["CONAN_CXX_FLAGS"] = "/MP%s" % cpus
-                ret["CONAN_C_FLAGS"] = "/MP%s" % cpus
+                flag = parallel_compiler_cl_flag()
+                ret["CONAN_CXX_FLAGS"] = flag
+                ret["CONAN_C_FLAGS"] = flag
 
         # fpic
         if str(self._os) not in ["Windows", "WindowsStore"]:
@@ -431,6 +431,7 @@ class CMake(object):
                     self._compiler_version and Version(self._compiler_version) >= "10":
                 if "--" not in args:
                     args.append("--")
+                # Parallel for building projects in the solution
                 args.append("/m:%i" % cpu_count())
 
         arg_list = join_arguments([
diff --git a/conans/client/build/compiler_flags.py b/conans/client/build/compiler_flags.py
index 9ec94021f..a8f857dfc 100644
--- a/conans/client/build/compiler_flags.py
+++ b/conans/client/build/compiler_flags.py
@@ -9,7 +9,7 @@
     #   -LIBPATH, -D, -I, -ZI and so on.
 
 """
-
+from conans import tools
 from conans.tools import unix_path
 
 
@@ -192,3 +192,8 @@ def format_library_paths(library_paths, win_bash=False, subsystem=None, compiler
 def format_libraries(libraries, compiler=None):
     pattern = "%s.lib" if str(compiler) == 'Visual Studio' else "-l%s"
     return [pattern % library for library in libraries if library]
+
+
+def parallel_compiler_cl_flag():
+    cpu_count = tools.cpu_count()
+    return "/MP%s" % cpu_count
diff --git a/conans/client/build/msbuild.py b/conans/client/build/msbuild.py
index 864729dfb..3ffb40051 100644
--- a/conans/client/build/msbuild.py
+++ b/conans/client/build/msbuild.py
@@ -26,7 +26,10 @@ class MSBuild(object):
 
     def build(self, project_file, targets=None, upgrade_project=True, build_type=None, arch=None,
               parallel=True, force_vcvars=False, toolset=None, platforms=None, use_env=True,
-              vcvars_ver=None, winsdk_version=None):
+              vcvars_ver=None, winsdk_version=None, properties=None):
+
+        self.build_env.parallel = parallel
+
         with tools.environment_append(self.build_env.vars):
             # Path for custom properties file
             props_file_contents = self._get_props_file_contents()
@@ -37,15 +40,16 @@ class MSBuild(object):
                                            targets=targets, upgrade_project=upgrade_project,
                                            build_type=build_type, arch=arch, parallel=parallel,
                                            toolset=toolset, platforms=platforms,
-                                           use_env=use_env)
+                                           use_env=use_env, properties=properties)
                 command = "%s && %s" % (vcvars, command)
                 return self._conanfile.run(command)
 
     def get_command(self, project_file, props_file_path=None, targets=None, upgrade_project=True,
                     build_type=None, arch=None, parallel=True, toolset=None, platforms=None,
-                    use_env=False):
+                    use_env=False, properties=None):
 
         targets = targets or []
+        properties = properties or {}
         command = []
 
         if upgrade_project and not get_env("CONAN_SKIP_VS_PROJECTS_UPGRADE", False):
@@ -101,6 +105,9 @@ class MSBuild(object):
         if props_file_path:
             command.append('/p:ForceImportBeforeCppTargets="%s"' % props_file_path)
 
+        for name, value in properties.items():
+            command.append('/p:%s="%s"' % (name, value))
+
         return " ".join(command)
 
     def _get_props_file_contents(self):
diff --git a/conans/client/build/visual_environment.py b/conans/client/build/visual_environment.py
index 86b8554f9..c3f530d22 100644
--- a/conans/client/build/visual_environment.py
+++ b/conans/client/build/visual_environment.py
@@ -2,7 +2,7 @@ import copy
 import os
 
 from conans.client.build.compiler_flags import build_type_define, build_type_flags, visual_runtime, format_defines, \
-    include_path_option
+    include_path_option, parallel_compiler_cl_flag
 from conans.client.build.cppstd_flags import cppstd_flag
 
 
@@ -33,6 +33,7 @@ class VisualStudioBuildEnvironment(object):
         self.cxx_flags = copy.copy(self._deps_cpp_info.cppflags)
         self.link_flags = self._configure_link_flags()
         self.std = self._std_cpp()
+        self.parallel = False
 
     def _configure_link_flags(self):
         ret = copy.copy(self._deps_cpp_info.exelinkflags)
@@ -61,6 +62,9 @@ class VisualStudioBuildEnvironment(object):
         ret.extend(self.cxx_flags)
         ret.extend(self.link_flags)
 
+        if self.parallel:  # Build source in parallel
+            ret.append(parallel_compiler_cl_flag())
+
         if self.std:
             ret.append(self.std)
 
diff --git a/conans/client/cmd/uploader.py b/conans/client/cmd/uploader.py
index 386bd4c82..fd8fab32f 100644
--- a/conans/client/cmd/uploader.py
+++ b/conans/client/cmd/uploader.py
@@ -6,7 +6,7 @@ from conans.util.log import logger
 from conans.client.loader_parse import load_conanfile_class
 from conans.paths import EXPORT_SOURCES_TGZ_NAME
 from conans.client.source import complete_recipe_sources
-from conans.search.search import search_recipes
+from conans.search.search import search_recipes, search_packages
 
 
 def _is_a_reference(ref):
@@ -28,7 +28,8 @@ class CmdUpload(object):
 
     def upload(self, recorder, reference_or_pattern, package_id=None, all_packages=None,
                force=False, confirm=False, retry=0, retry_wait=0, skip_upload=False,
-               integrity_check=False, no_overwrite=None, remote_name=None):
+               integrity_check=False, no_overwrite=None, remote_name=None,
+               query=None):
         """If package_id is provided, conan_reference_or_pattern is a ConanFileReference"""
 
         if package_id and not _is_a_reference(reference_or_pattern):
@@ -59,6 +60,9 @@ class CmdUpload(object):
                                              str(conan_ref)))
                 if all_packages:
                     packages_ids = self._client_cache.conan_packages(conan_ref)
+                elif query:
+                    packages = search_packages(self._client_cache, conan_ref, query)
+                    packages_ids = list(packages.keys())
                 elif package_id:
                     packages_ids = [package_id, ]
                 else:
diff --git a/conans/client/command.py b/conans/client/command.py
index 1df046338..f69abcd30 100644
--- a/conans/client/command.py
+++ b/conans/client/command.py
@@ -55,6 +55,9 @@ class OnceArgument(argparse.Action):
             raise argparse.ArgumentError(None, msg)
         setattr(namespace, self.dest, values)
 
+_QUERY_EXAMPLE = ("os=Windows AND (arch=x86 OR compiler=gcc)")
+_PATTERN_EXAMPLE = ("boost/*")
+_REFERENCE_EXAMPLE =  ("MyPackage/1.2@user/channel")
 
 _BUILD_FOLDER_HELP = ("Directory for the build process. Defaulted to the current directory. A "
                       "relative path to current directory can also be specified")
@@ -62,12 +65,12 @@ _INSTALL_FOLDER_HELP = ("Directory containing the conaninfo.txt and conanbuildin
                         "(from previous 'conan install'). Defaulted to --build-folder")
 _KEEP_SOURCE_HELP = ("Do not remove the source folder in local cache, even if the recipe changed. "
                      "Use this for testing purposes only")
-_PATTERN_OR_REFERENCE_HELP = ("Pattern or package recipe reference, e.g., 'boost/*', "
-                              "'MyPackage/1.2@user/channel'")
+_PATTERN_OR_REFERENCE_HELP = ("Pattern or package recipe reference, e.g., '%s', "
+                              "'%s'" % (_REFERENCE_EXAMPLE, _PATTERN_EXAMPLE))
 _PATH_HELP = ("Path to a folder containing a conanfile.py or to a recipe file "
               "e.g., my_folder/conanfile.py")
-_QUERY_HELP = ("Packages query: 'os=Windows AND (arch=x86 OR compiler=gcc)'. The "
-               "'pattern_or_reference' parameter has to be a reference: MyPackage/1.2@user/channel")
+_QUERY_HELP = ("Packages query: '%s'. The 'pattern_or_reference' parameter has "
+               "to be a reference: %s" % (_QUERY_EXAMPLE, _REFERENCE_EXAMPLE))
 _SOURCE_FOLDER_HELP = ("Directory containing the sources. Defaulted to the conanfile's directory. A"
                        " relative path to current directory can also be specified")
 
@@ -733,6 +736,9 @@ class Command(object):
         parser.add_argument("-l", "--locks", default=False, action="store_true",
                             help="Remove locks")
         args = parser.parse_args(*args)
+
+        # NOTE: returns the expanded pattern (if a pattern was given), and checks
+        # that the query parameter wasn't abused
         reference = self._check_query_parameter_and_get_reference(args.pattern_or_reference,
                                                                   args.query)
 
@@ -934,6 +940,8 @@ class Command(object):
         parser.add_argument('pattern_or_reference', help=_PATTERN_OR_REFERENCE_HELP)
         parser.add_argument("-p", "--package", default=None, action=OnceArgument,
                             help='package ID to upload')
+        parser.add_argument('-q', '--query', default=None, action=OnceArgument,
+                            help="Only upload packages matching a specific query. " + _QUERY_HELP)
         parser.add_argument("-r", "--remote", action=OnceArgument,
                             help='upload to this specific remote')
         parser.add_argument("--all", action='store_true', default=False,
@@ -959,11 +967,15 @@ class Command(object):
 
         args = parser.parse_args(*args)
 
+        if args.query and args.package:
+            raise ConanException("'-q' and '-p' parameters can't be used at the same time")
+
         cwd = os.getcwd()
         info = None
 
         try:
             info = self._conan.upload(pattern=args.pattern_or_reference, package=args.package,
+                                      query=args.query,
                                       remote_name=args.remote, all_packages=args.all,
                                       force=args.force,
                                       confirm=args.confirm, retry=args.retry,
diff --git a/conans/client/conan_api.py b/conans/client/conan_api.py
index 50a381c3c..d96295482 100644
--- a/conans/client/conan_api.py
+++ b/conans/client/conan_api.py
@@ -48,7 +48,6 @@ from conans.client.remover import ConanRemover
 from conans.client.cmd.download import download
 from conans.model.workspace import Workspace
 from conans.client.graph.graph_manager import GraphManager
-from conans.client.graph.graph import BINARY_BUILD
 
 
 default_manifest_folder = '.conan_manifests'
@@ -728,9 +727,10 @@ class ConanAPIV1(object):
         return recorder.get_info()
 
     @api_method
-    def upload(self, pattern, package=None, remote_name=None, all_packages=False, force=False,
-               confirm=False, retry=2, retry_wait=5, skip_upload=False, integrity_check=False,
-               no_overwrite=None):
+    def upload(self, pattern, package=None, remote_name=None,
+               all_packages=False, force=False, confirm=False, retry=2,
+               retry_wait=5, skip_upload=False, integrity_check=False,
+               no_overwrite=None, query=None):
         """ Uploads a package recipe and the generated binary packages to a specified remote
         """
 
@@ -746,7 +746,8 @@ class ConanAPIV1(object):
                              self._registry)
         try:
             uploader.upload(recorder, pattern, package, all_packages, force, confirm, retry,
-                            retry_wait, skip_upload, integrity_check, no_overwrite, remote_name)
+                            retry_wait, skip_upload, integrity_check, no_overwrite, remote_name,
+                            query=query)
             return recorder.get_info()
         except ConanException as exc:
             recorder.error = True
diff --git a/conans/client/tools/win.py b/conans/client/tools/win.py
index 2995a20be..8f963e837 100644
--- a/conans/client/tools/win.py
+++ b/conans/client/tools/win.py
@@ -241,12 +241,13 @@ def vcvars_command(settings, arch=None, compiler_version=None, force=False, vcva
 
     # https://msdn.microsoft.com/en-us/library/f2ccy3wt.aspx
     arch_setting = arch_setting or 'x86_64'
-    if detected_architecture() == 'x86_64':
-        vcvars_arch = {'x86': 'x86',
+    arch_build = settings.get_safe("arch_build") or detected_architecture()
+    if arch_build == 'x86_64':
+        vcvars_arch = {'x86': 'amd64_x86' if int(compiler_version) >= 12 else 'x86',
                        'x86_64': 'amd64',
                        'armv7': 'amd64_arm',
                        'armv8': 'amd64_arm64'}.get(arch_setting)
-    elif detected_architecture() == 'x86':
+    elif arch_build == 'x86':
         vcvars_arch = {'x86': 'x86',
                        'x86_64': 'x86_amd64',
                        'armv7': 'x86_arm',


Test patch:
diff --git a/conans/test/build_helpers/msbuild_test.py b/conans/test/build_helpers/msbuild_test.py
index 10b45eee3..c91726fce 100644
--- a/conans/test/build_helpers/msbuild_test.py
+++ b/conans/test/build_helpers/msbuild_test.py
@@ -110,6 +110,15 @@ class HelloConan(ConanFile):
         self.assertIn("Debug|x86", client.user_io.out)
         self.assertIn("Copied 1 '.exe' file: MyProject.exe", client.user_io.out)
 
+    def custom_properties_test(self):
+        settings = MockSettings({"build_type": "Debug",
+                                 "compiler": "Visual Studio",
+                                 "arch": "x86_64"})
+        conanfile = MockConanfile(settings)
+        msbuild = MSBuild(conanfile)
+        command = msbuild.get_command("project_file.sln", properties={"MyProp1": "MyValue1", "MyProp2": "MyValue2"})
+        self.assertIn(' /p:MyProp1="MyValue1" /p:MyProp2="MyValue2"', command)
+
     @unittest.skipUnless(platform.system() == "Windows", "Requires MSBuild")
     def reuse_msbuild_object_test(self):
         # https://github.com/conan-io/conan/issues/2865
diff --git a/conans/test/build_helpers/visual_environment_test.py b/conans/test/build_helpers/visual_environment_test.py
index a5c7caa14..89033f31d 100644
--- a/conans/test/build_helpers/visual_environment_test.py
+++ b/conans/test/build_helpers/visual_environment_test.py
@@ -39,6 +39,23 @@ class VisualStudioBuildEnvironmentTest(unittest.TestCase):
                    '-mysharedlinkflag'],
             "LIB": ["/one/lib/path", "/two/lib/path"],
         })
+        tool.parallel = True
+        self.assertEquals(tool.vars_dict, {
+            "CL": ["-I/one/include/path", "-I/two/include/path",
+                   '-MDd',
+                   '-mycflag',
+                   '-mycflag2',
+                   '-Zi',
+                   '-Ob0',
+                   '-Od',
+                   '-mycppflag',
+                   '-mycppflag2',
+                   '-myexelinkflag',
+                   '-mysharedlinkflag',
+                   '/MP%s' % tools.cpu_count()],
+            "LIB": ["/one/lib/path", "/two/lib/path"],
+        })
+        tool.parallel = False
 
         # Now alter the paths before the vars_dict call
         tool.include_paths.append("/three/include/path")
diff --git a/conans/test/command/upload_test.py b/conans/test/command/upload_test.py
index 193024241..d53a05efe 100644
--- a/conans/test/command/upload_test.py
+++ b/conans/test/command/upload_test.py
@@ -5,6 +5,7 @@ from conans.test.utils.cpp_test_files import cpp_hello_conan_files
 from conans.model.ref import ConanFileReference, PackageReference
 from conans.util.files import save, is_dirty, gzopen_without_timestamps
 import os
+import itertools
 from mock import mock
 from conans.errors import ConanException
 from conans.paths import EXPORT_SOURCES_TGZ_NAME, PACKAGE_TGZ_NAME
@@ -20,6 +21,16 @@ class MyPkg(ConanFile):
         self.copy("*")
 """
 
+conanfile_upload_query = """from conans import ConanFile
+class MyPkg(ConanFile):
+    name = "Hello1"
+    version = "1.2.1"
+    exports_sources = "*"
+    settings = "os", "arch"
+
+    def package(self):
+        self.copy("*")
+"""
 
 class UploadTest(unittest.TestCase):
 
@@ -78,6 +89,38 @@ class UploadTest(unittest.TestCase):
         self.assertIn("Uploading conan_package.tgz", client.user_io.out)
         self.assertIn("Uploading conanfile.py", client.user_io.out)
 
+    def query_upload_test(self):
+        client = self._client()
+        client.save({"conanfile.py": conanfile_upload_query})
+
+        for os, arch in itertools.product(["Macos", "Linux", "Windows"],
+                                          ["armv8", "x86_64"]):
+            client.run("create . user/testing -s os=%s -s arch=%s" % (os, arch))
+
+        # Check that the right number of packages are picked up by the queries
+        client.run("upload Hello1/*@user/testing --confirm -q 'os=Windows or os=Macos'")
+        for i in range(1, 5):
+            self.assertIn("Uploading package %d/4" % i, client.user_io.out)
+        self.assertNotIn("Package is up to date, upload skipped", client.user_io.out)
+
+        client.run("upload Hello1/*@user/testing --confirm -q 'os=Linux and arch=x86_64'")
+        self.assertIn("Uploading package 1/1", client.user_io.out)
+
+        client.run("upload Hello1/*@user/testing --confirm -q 'arch=armv8'")
+        for i in range(1, 4):
+            self.assertIn("Uploading package %d/3" % i, client.user_io.out)
+        self.assertIn("Package is up to date, upload skipped", client.user_io.out)
+
+        # Check that a query not matching any packages doesn't upload any packages
+        client.run("upload Hello1/*@user/testing --confirm -q 'arch=sparc'")
+        self.assertNotIn("Uploading package", client.user_io.out)
+
+        # Check that an invalid query fails
+        try:
+            client.run("upload Hello1/*@user/testing --confirm -q 'blah blah blah'")
+        except:
+            self.assertIn("Invalid package query", client.user_io.out)
+
     def broken_sources_tgz_test(self):
         # https://github.com/conan-io/conan/issues/2854
         client = self._client()
@@ -110,8 +153,9 @@ class UploadTest(unittest.TestCase):
         client.save({"conanfile.py": conanfile,
                      "source.h": "my source"})
         client.run("create . user/testing")
-        package_ref = PackageReference.loads("Hello0/1.2.1@user/testing:"
-                                             "5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9")
+        CONAN_PACKAGE_ID = "5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9"
+        package_ref = PackageReference.loads("Hello0/1.2.1@user/testing:" +
+                                             CONAN_PACKAGE_ID)
 
         def gzopen_patched(name, mode="r", fileobj=None, compresslevel=None, **kwargs):
             if name == PACKAGE_TGZ_NAME:
@@ -128,8 +172,8 @@ class UploadTest(unittest.TestCase):
             self.assertTrue(is_dirty(tgz))
 
         client.run("upload * --confirm --all")
-        self.assertIn("WARN: Hello0/1.2.1@user/testing:5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9: "
-                      "Removing conan_package.tgz, marked as dirty",
+        self.assertIn("WARN: Hello0/1.2.1@user/testing:%s: "
+                      "Removing conan_package.tgz, marked as dirty" % CONAN_PACKAGE_ID,
                       client.out)
         self.assertTrue(os.path.exists(tgz))
         self.assertFalse(is_dirty(tgz))
diff --git a/conans/test/model/fake_retriever.py b/conans/test/model/fake_retriever.py
new file mode 100644
index 000000000..00a14f2da
--- /dev/null
+++ b/conans/test/model/fake_retriever.py
@@ -0,0 +1,29 @@
+import os
+
+from conans.test.utils.test_files import temp_folder
+from conans.model.ref import ConanFileReference
+from conans.tools import save
+from conans.paths import CONANFILE
+
+
+class Retriever(object):
+    def __init__(self, loader, output):
+        self.loader = loader
+        self.output = output
+        self.folder = temp_folder()
+
+    def root(self, content):
+        conan_path = os.path.join(self.folder, "root.py")
+        save(conan_path, content)
+        conanfile = self.loader.load_conan(conan_path, self.output, consumer=True)
+        return conanfile
+
+    def conan(self, conan_ref, content):
+        if isinstance(conan_ref, str):
+            conan_ref = ConanFileReference.loads(conan_ref)
+        conan_path = os.path.join(self.folder, "/".join(conan_ref), CONANFILE)
+        save(conan_path, content)
+
+    def get_recipe(self, conan_ref, check_updates, update, remote_name, recorder):  # @UnusedVariable
+        conan_path = os.path.join(self.folder, "/".join(conan_ref), CONANFILE)
+        return conan_path, None, None, conan_ref
diff --git a/conans/test/model/transitive_reqs_test.py b/conans/test/model/transitive_reqs_test.py
index 7b0f08c53..727e6aef3 100644
--- a/conans/test/model/transitive_reqs_test.py
+++ b/conans/test/model/transitive_reqs_test.py
@@ -1,45 +1,18 @@
 import unittest
-import os
-
 from collections import namedtuple
 
 from conans.test.utils.tools import TestBufferConanOutput
-from conans.paths import CONANFILE
 from conans.client.graph.graph_builder import DepsGraphBuilder
 from conans.model.ref import ConanFileReference
 from conans.model.options import OptionsValues, option_not_exist_msg, option_wrong_value_msg
 from conans.client.loader import ConanFileLoader
-from conans.util.files import save
 from conans.model.settings import Settings, bad_value_msg
 from conans.errors import ConanException
 from conans.model.requires import Requirements
 from conans.client.conf import default_settings_yml
 from conans.model.values import Values
-from conans.test.utils.test_files import temp_folder
 from conans.model.profile import Profile
-
-
-class Retriever(object):
-    def __init__(self, loader, output):
-        self.loader = loader
-        self.output = output
-        self.folder = temp_folder()
-
-    def root(self, content):
-        conan_path = os.path.join(self.folder, "root")
-        save(conan_path, content)
-        conanfile = self.loader.load_conan(conan_path, self.output, consumer=True)
-        return conanfile
-
-    def conan(self, conan_ref, content):
-        if isinstance(conan_ref, str):
-            conan_ref = ConanFileReference.loads(conan_ref)
-        conan_path = os.path.join(self.folder, "/".join(conan_ref), CONANFILE)
-        save(conan_path, content)
-
-    def get_recipe(self, conan_ref, check_updates, update, remote_name, recorder):  # @UnusedVariable
-        conan_path = os.path.join(self.folder, "/".join(conan_ref), CONANFILE)
-        return conan_path, None, None, conan_ref
+from conans.test.model.fake_retriever import Retriever
 
 
 say_content = """
@@ -1685,10 +1658,10 @@ class SayConan(ConanFile):
         check(conanfile, "myoption=1", "os=Linux")
 
     def test_errors(self):
-        with self.assertRaisesRegexp(ConanException, "root: No subclass of ConanFile"):
+        with self.assertRaisesRegexp(ConanException, "root.py: No subclass of ConanFile"):
             self.root("")
 
-        with self.assertRaisesRegexp(ConanException, "root: More than 1 conanfile in the file"):
+        with self.assertRaisesRegexp(ConanException, "root.py: More than 1 conanfile in the file"):
             self.root("""from conans import ConanFile
 class HelloConan(ConanFile):pass
 class ByeConan(ConanFile):pass""")
diff --git a/conans/test/model/version_ranges_test.py b/conans/test/model/version_ranges_test.py
index 3e0122605..3c59fc338 100644
--- a/conans/test/model/version_ranges_test.py
+++ b/conans/test/model/version_ranges_test.py
@@ -1,20 +1,18 @@
-import os
 import unittest
 from collections import namedtuple, Counter
 
 from conans.test.utils.tools import TestBufferConanOutput
-from conans.paths import CONANFILE, SimplePaths
+from conans.paths import SimplePaths
 from conans.client.graph.graph_builder import DepsGraphBuilder
 from conans.model.ref import ConanFileReference
 from conans.client.loader import ConanFileLoader
 from conans.model.settings import Settings
 from conans.model.requires import Requirements
-from conans.test.utils.test_files import temp_folder
 from conans.client.graph.range_resolver import RangeResolver, satisfying
 from parameterized import parameterized
 from conans.model.profile import Profile
 from conans.errors import ConanException
-from conans.util.files import save
+from conans.test.model.fake_retriever import Retriever
 
 
 class BasicMaxVersionTest(unittest.TestCase):
@@ -88,29 +86,6 @@ class BasicMaxVersionTest(unittest.TestCase):
         self.assertEqual(result, "2.1.1")
 
 
-class Retriever(object):
-    def __init__(self, loader, output):
-        self.loader = loader
-        self.output = output
-        self.folder = temp_folder()
-
-    def root(self, content):
-        conan_path = os.path.join(self.folder, "root")
-        save(conan_path, content)
-        conanfile = self.loader.load_conan(conan_path, self.output, consumer=True)
-        return conanfile
-
-    def conan(self, conan_ref, content):
-        if isinstance(conan_ref, str):
-            conan_ref = ConanFileReference.loads(conan_ref)
-        conan_path = os.path.join(self.folder, "/".join(conan_ref), CONANFILE)
-        save(conan_path, content)
-
-    def get_recipe(self, conan_ref, check_updates, update, remote_name, recorder):  # @UnusedVariables
-        conan_path = os.path.join(self.folder, "/".join(conan_ref), CONANFILE)
-        return conan_path, None, None, conan_ref
-
-
 hello_content = """
 from conans import ConanFile
 
diff --git a/conans/test/util/tools_test.py b/conans/test/util/tools_test.py
index 1637200da..13ba6eb5f 100644
--- a/conans/test/util/tools_test.py
+++ b/conans/test/util/tools_test.py
@@ -656,6 +656,23 @@ class HelloConan(ConanFile):
             self.assertIn("Conan:vcvars already set", str(output))
             self.assertIn("VS140COMNTOOLS=", str(output))
 
+    @unittest.skipUnless(platform.system() == "Windows", "Requires Windows")
+    def vcvars_amd64_32_cross_building_support_test(self):
+        # amd64_x86 crossbuilder
+        settings = Settings.loads(default_settings_yml)
+        settings.os = "Windows"
+        settings.compiler = "Visual Studio"
+        settings.compiler.version = "15"
+        settings.arch = "x86"
+        settings.arch_build = "x86_64"
+        cmd = tools.vcvars_command(settings)
+        self.assertIn('vcvarsall.bat" amd64_x86', cmd)
+
+        # It follows arch_build first
+        settings.arch_build = "x86"
+        cmd = tools.vcvars_command(settings)
+        self.assertIn('vcvarsall.bat" x86', cmd)
+
     def vcvars_raises_when_not_found_test(self):
         text = """
 os: [Windows]