Repository: conan-io/conan

Issue:
[question] Are there environment difference between the "developer workflow" and `conan create`?
While moving one of our _fat_ dependencies from prebuilt binaries (packaged through Conan ; ) to a recipe actually building the thing, we are encountering a complication we never encountered for our other recipes:

We wrote the recipe, following the recommendend [Package Development Flow](https://docs.conan.io/en/latest/developing_packages/package_dev_flow.html#package-development-flow). The end to end flow works without any error, notably `conan build` command.

Yet, when moving to the final step of calling `conan create` on the exact same recipe, the process fails during the `build()`  step.

Are there known difference between the environments used by the "developer workflow" and `conan create` command that we could investigate to find the source of this failure?

Discussion:

Patch:
diff --git a/conans/client/build/cmake.py b/conans/client/build/cmake.py
index 0dae3d4e5..f10e25ff3 100644
--- a/conans/client/build/cmake.py
+++ b/conans/client/build/cmake.py
@@ -4,15 +4,17 @@ import subprocess
 from itertools import chain
 
 from conans.client import defs_to_string, join_arguments, tools
-from conans.client.build.cmake_flags import CMakeDefinitionsBuilder, build_type_definition, \
-    cmake_in_local_cache_var_name, cmake_install_prefix_var_name, get_generator, get_toolset, \
-    is_multi_configuration, runtime_definition_var_name, verbose_definition, verbose_definition_name
-from conans.client.tools.oss import args_to_string, cpu_count
+from conans.client.build.cmake_flags import CMakeDefinitionsBuilder, \
+    get_generator, is_multi_configuration, verbose_definition, verbose_definition_name, \
+    cmake_install_prefix_var_name, get_toolset, build_type_definition, \
+    cmake_in_local_cache_var_name, runtime_definition_var_name
 from conans.errors import ConanException
 from conans.model.conan_file import ConanFile
 from conans.model.version import Version
+from conans.client import tools
+from conans.client.tools.oss import cpu_count, args_to_string
 from conans.util.config_parser import get_bool_from_text
-from conans.util.files import decode_text, get_abs_path, mkdir, walk
+from conans.util.files import mkdir, get_abs_path, walk, decode_text
 
 
 class CMake(object):
@@ -21,12 +23,12 @@ class CMake(object):
                  parallel=True, build_type=None, toolset=None, make_program=None,
                  set_cmake_flags=False):
         """
-        :param settings_or_conanfile: Conanfile instance (or settings for retro compatibility)
+        :param conanfile: Conanfile instance
         :param generator: Generator name to use or none to autodetect
         :param cmake_system_name: False to not use CMAKE_SYSTEM_NAME variable,
                True for auto-detect or directly a string with the system name
         :param parallel: Try to build with multiple cores if available
-        :param build_type: Overrides default build type comming from settings
+        :param build_type: Overrides default build type coming from settings
         :param toolset: Toolset name to use (such as llvm-vs2014) or none for default one,
                 applies only to certain generators (e.g. Visual Studio)
         :param set_cmake_flags: whether or not to set CMake flags like CMAKE_CXX_FLAGS, CMAKE_C_FLAGS, etc.
@@ -35,30 +37,25 @@ class CMake(object):
         if not isinstance(conanfile, ConanFile):
             raise ConanException("First argument of CMake() has to be ConanFile. Use CMake(self)")
 
-        self._settings = conanfile.settings
         self._conanfile = conanfile
+        self._settings = conanfile.settings
+        self._build_type = build_type or conanfile.settings.get_safe("build_type")
 
-        self.generator = generator or get_generator(self._settings)
-        self.toolset = toolset or get_toolset(self._settings)
-        self.build_dir = None
+        self.generator = generator or get_generator(conanfile.settings)
         self.parallel = parallel
-
-        self._set_cmake_flags = set_cmake_flags
-        self._cmake_system_name = cmake_system_name
-        self._make_program = make_program
-
         # Initialize definitions (won't be updated if conanfile or any of these variables change)
-        builder = CMakeDefinitionsBuilder(self._conanfile, cmake_system_name=self._cmake_system_name,
-                                          make_program=self._make_program, parallel=self.parallel,
-                                          generator=self.generator,
-                                          set_cmake_flags=self._set_cmake_flags,
+        builder = CMakeDefinitionsBuilder(self._conanfile,
+                                          cmake_system_name=cmake_system_name,
+                                          make_program=make_program, parallel=parallel,
+                                          generator=self.generator, set_cmake_flags=set_cmake_flags,
+                                          forced_build_type=build_type,
                                           output=self._conanfile.output)
-        self.definitions = builder.get_definitions()
+        # FIXME CONAN 2.0: CMake() interface should be always the constructor and self.definitions.
+        # FIXME CONAN 2.0: Avoid properties and attributes to make the user interface more clear
 
-        if build_type is None:
-            self.build_type = self._settings.get_safe("build_type")
-        else:
-            self.build_type = build_type
+        self.definitions = builder.get_definitions()
+        self.toolset = toolset or get_toolset(self._settings)
+        self.build_dir = None
 
     @property
     def build_folder(self):
@@ -76,11 +73,23 @@ class CMake(object):
     def build_type(self, build_type):
         settings_build_type = self._settings.get_safe("build_type")
         if build_type != settings_build_type:
-            self._conanfile.output.warn(
-                'Set CMake build type "%s" is different than the settings build_type "%s"'
-                % (build_type, settings_build_type))
+            self._conanfile.output.warn("Forced CMake build type ('%s') different from the settings"
+                                        " build type ('%s')" % (build_type, settings_build_type))
+        self.definitions.pop("CMAKE_BUILD_TYPE", None)
+        self.definitions.update(build_type_definition(build_type, self.generator))
         self._build_type = build_type
-        self.definitions.update(build_type_definition(self._build_type, self.generator))
+
+    @property
+    def in_local_cache(self):
+        try:
+            in_local_cache = self.definitions[cmake_in_local_cache_var_name]
+            return get_bool_from_text(str(in_local_cache))
+        except KeyError:
+            return False
+
+    @property
+    def runtime(self):
+        return defs_to_string(self.definitions.get(runtime_definition_var_name))
 
     @property
     def flags(self):
@@ -100,10 +109,6 @@ class CMake(object):
             args.append('-T "%s"' % self.toolset)
         return join_arguments(args)
 
-    @property
-    def runtime(self):
-        return defs_to_string(self.definitions.get(runtime_definition_var_name))
-
     @property
     def build_config(self):
         """ cmake --build tool have a --config option for Multi-configuration IDEs
@@ -138,10 +143,12 @@ class CMake(object):
     def _run(self, command):
         compiler = self._settings.get_safe("compiler")
         the_os = self._settings.get_safe("os")
-        is_clangcl = the_os == 'Windows' and compiler == 'clang'
-        is_msvc = compiler == 'Visual Studio'
-        if (is_msvc or is_clangcl) and self.generator in ['Ninja', 'NMake Makefiles', 'NMake Makefiles JOM']:
-            with tools.vcvars(self._settings, force=True, filter_known_paths=False):
+        is_clangcl = the_os == "Windows" and compiler == "clang"
+        is_msvc = compiler == "Visual Studio"
+        if (is_msvc or is_clangcl) and self.generator in ["Ninja", "NMake Makefiles",
+                                                          "NMake Makefiles JOM"]:
+            with tools.vcvars(self._settings, force=True, filter_known_paths=False,
+                              output=self._conanfile.output):
                 self._conanfile.run(command)
         else:
             self._conanfile.run(command)
@@ -246,14 +253,6 @@ class CMake(object):
     def verbose(self, value):
         self.definitions.update(verbose_definition(value))
 
-    @property
-    def in_local_cache(self):
-        try:
-            in_local_cache = self.definitions[cmake_in_local_cache_var_name]
-            return get_bool_from_text(str(in_local_cache))
-        except KeyError:
-            return False
-
     def patch_config_paths(self):
         """
         changes references to the absolute path of the installed package and its dependencies in
diff --git a/conans/client/build/cmake_flags.py b/conans/client/build/cmake_flags.py
index c422b610f..3d73cf907 100644
--- a/conans/client/build/cmake_flags.py
+++ b/conans/client/build/cmake_flags.py
@@ -101,19 +101,17 @@ def build_type_definition(build_type, generator):
 class CMakeDefinitionsBuilder(object):
 
     def __init__(self, conanfile, cmake_system_name=True, make_program=None,
-                 parallel=True, generator=None, set_cmake_flags=False, output=None):
+                 parallel=True, generator=None, set_cmake_flags=False,
+                 forced_build_type=None, output=None):
         self._conanfile = conanfile
         self._forced_cmake_system_name = cmake_system_name
         self._make_program = make_program
         self._parallel = parallel
-        self._forced_generator = generator
+        self._generator = generator or get_generator(self._conanfile.settings)
         self._set_cmake_flags = set_cmake_flags
+        self._forced_build_type = forced_build_type
         self._output = output
 
-    @property
-    def generator(self):
-        return self._forced_generator or get_generator(self._conanfile.settings)
-
     def _ss(self, setname):
         """safe setting"""
         return self._conanfile.settings.get_safe(setname)
@@ -222,10 +220,10 @@ class CMakeDefinitionsBuilder(object):
         make_program = os.getenv("CONAN_MAKE_PROGRAM") or self._make_program
         if make_program:
             if not tools.which(make_program):
-                self._conanfile.output.warn("The specified make program '%s' cannot be found"
-                                            "and will be ignored" % make_program)
+                self._output.warn("The specified make program '%s' cannot be found and will be "
+                                  "ignored" % make_program)
             else:
-                self._conanfile.output.info("Using '%s' as CMAKE_MAKE_PROGRAM" % make_program)
+                self._output.info("Using '%s' as CMAKE_MAKE_PROGRAM" % make_program)
                 return {"CMAKE_MAKE_PROGRAM": make_program}
 
         return {}
@@ -241,9 +239,15 @@ class CMakeDefinitionsBuilder(object):
         build_type = self._ss("build_type")
 
         ret = OrderedDict()
-        ret.update(build_type_definition(build_type, self.generator))
         ret.update(runtime_definition(runtime))
 
+        if self._forced_build_type and self._forced_build_type != build_type:
+            self._output.warn("Forced CMake build type ('%s') different from the settings build "
+                              "type ('%s')" % (self._forced_build_type, build_type))
+            ret.update(build_type_definition(self._forced_build_type, self._generator))
+        else:
+            ret.update(build_type_definition(build_type, self._generator))
+
         if str(os_) == "Macos":
             if arch == "x86":
                 ret["CMAKE_OSX_ARCHITECTURES"] = "i386"
diff --git a/conans/client/cmd/export.py b/conans/client/cmd/export.py
index 910842592..cc38d61eb 100644
--- a/conans/client/cmd/export.py
+++ b/conans/client/cmd/export.py
@@ -69,12 +69,17 @@ def _capture_export_scm_data(conanfile, conanfile_dir, destination_folder, outpu
         os.unlink(scm_src_file)
 
     scm_data = get_scm_data(conanfile)
-    captured_revision = scm_data.capture_revision if scm_data else False
+    if not scm_data:
+        return None, False
 
-    if not scm_data or not (scm_data.capture_origin or scm_data.capture_revision):
-        return None, captured_revision
+    # Resolve SCMData in the user workspace (someone may want to access CVS or import some py)
+    captured_revision = scm_data.capture_revision
 
     scm = SCM(scm_data, conanfile_dir, output)
+    if scm_data.capture_origin or scm_data.capture_revision:
+        # Generate the scm_folder.txt file pointing to the src_path
+        src_path = scm.get_repo_root()
+        save(scm_src_file, src_path.replace("\\", "/"))
 
     if scm_data.url == "auto":
         origin = scm.get_qualified_remote_url()
@@ -84,17 +89,14 @@ def _capture_export_scm_data(conanfile, conanfile_dir, destination_folder, outpu
             output.warn("Repo origin looks like a local path: %s" % origin)
         output.success("Repo origin deduced by 'auto': %s" % origin)
         scm_data.url = origin
+
     if scm_data.revision == "auto":
         if not scm.is_pristine():
             output.warn("Repo status is not pristine: there might be modified files")
         scm_data.revision = scm.get_revision()
         output.success("Revision deduced by 'auto': %s" % scm_data.revision)
 
-    # Generate the scm_folder.txt file pointing to the src_path
-    src_path = scm.get_repo_root()
-    save(scm_src_file, src_path.replace("\\", "/"))
-    _replace_scm_data_in_conanfile(os.path.join(destination_folder, "conanfile.py"),
-                                   scm_data)
+    _replace_scm_data_in_conanfile(os.path.join(destination_folder, "conanfile.py"), scm_data)
 
     return scm_data, captured_revision
 
@@ -146,7 +148,6 @@ def _replace_scm_data_in_conanfile(conanfile_path, scm_data):
     content = content if not headers else ''.join(headers) + content
     save(conanfile_path, content)
 
-
 def _export_conanfile(conanfile_path, output, client_cache, conanfile, conan_ref, keep_source):
 
     exports_folder = client_cache.export(conan_ref)
diff --git a/conans/client/conan_api.py b/conans/client/conan_api.py
index 357ce44ad..1d2b50a7c 100644
--- a/conans/client/conan_api.py
+++ b/conans/client/conan_api.py
@@ -556,14 +556,11 @@ class ConanAPIV1(object):
         self._client_cache.invalidate()
 
     @api_method
-    def config_install(self, item, verify_ssl, config_type=None, args=None):
-        # _make_abs_path, but could be not a path at all
-        if item is not None and os.path.exists(item) and not os.path.isabs(item):
-            item = os.path.abspath(item)
+    def config_install(self, path_or_url, verify_ssl, config_type=None, args=None):
 
         from conans.client.conf.config_installer import configuration_install
         requester = self._remote_manager._auth_manager._rest_client.requester,  # FIXME: Look out!
-        return configuration_install(item, self._client_cache, self._user_io.out, verify_ssl,
+        return configuration_install(path_or_url, self._client_cache, self._user_io.out, verify_ssl,
                                      requester=requester, config_type=config_type, args=args)
 
     def _info_args(self, reference, install_folder, profile_name, settings, options, env):
@@ -922,12 +919,12 @@ class ConanAPIV1(object):
 
     @api_method
     def get_path(self, reference, package_id=None, path=None, remote_name=None):
-        from conans.client.local_file_getter import get_path
         reference = ConanFileReference.loads(reference)
         if not path:
             path = "conanfile.py" if not package_id else "conaninfo.txt"
 
         if not remote_name:
+            from conans.client.local_file_getter import get_path
             return get_path(self._client_cache, reference, package_id, path), path
         else:
             remote = self.get_remote_by_name(remote_name)
diff --git a/conans/client/conf/config_installer.py b/conans/client/conf/config_installer.py
index 8559e6299..90f191ff9 100644
--- a/conans/client/conf/config_installer.py
+++ b/conans/client/conf/config_installer.py
@@ -1,12 +1,12 @@
 import os
 import shutil
-import subprocess
 
 from six.moves.urllib.parse import urlparse
 
 from conans import load
 from conans.client import tools
 from conans.client.remote_registry import load_registry_txt
+from conans.client.tools import Git
 from conans.client.tools.files import unzip
 from conans.errors import ConanException
 from conans.util.files import mkdir, rmdir, walk
@@ -38,24 +38,21 @@ def _handle_profiles(source_folder, target_folder, output):
             relative_path = ""
         for f in files:
             profile = os.path.join(relative_path, f)
-            output.info("    Installing profile %s" % profile)
+            output.info("  - %s" % profile)
             shutil.copy(os.path.join(root, f), os.path.join(target_folder, profile))
 
 
 def _process_git_repo(repo_url, client_cache, output, tmp_folder, verify_ssl, args=None):
-    output.info("Trying to clone repo  %s" % repo_url)
+    output.info("Trying to clone repo: %s" % repo_url)
 
     with tools.chdir(tmp_folder):
         try:
             args = args or ""
-            subprocess.check_output('git -c http.sslVerify=%s %s clone "%s" config'
-                                    % (verify_ssl, args, repo_url),
-                                    shell=True)
-            output.info("Repo cloned")
+            git = Git(verify_ssl=verify_ssl, output=output)
+            git.clone(repo_url, args=args)
+            output.info("Repo cloned!")
         except Exception as e:
-            raise ConanException("config install error. Can't clone repo: %s" % str(e))
-
-    tmp_folder = os.path.join(tmp_folder, "config")
+            raise ConanException("Can't clone repo: %s" % str(e))
     _process_folder(tmp_folder, client_cache, output)
 
 
@@ -84,7 +81,7 @@ def _process_folder(folder, client_cache, output):
                 conan_conf = client_cache.conan_config
                 _handle_conan_conf(conan_conf, os.path.join(root, f))
             elif f == "remotes.txt":
-                output.info("Defining remotes")
+                output.info("Defining remotes from remotes.txt")
                 _handle_remotes(client_cache, os.path.join(root, f))
             else:
                 relpath = os.path.relpath(root, folder)
@@ -94,7 +91,7 @@ def _process_folder(folder, client_cache, output):
                 shutil.copy(os.path.join(root, f), target_folder)
         for d in dirs:
             if d == "profiles":
-                output.info("Installing profiles")
+                output.info("Installing profiles:")
                 profiles_path = client_cache.profiles_path
                 _handle_profiles(os.path.join(root, d), profiles_path, output)
                 break
@@ -111,32 +108,76 @@ def _process_download(item, client_cache, output, tmp_folder, verify_ssl, reques
         raise ConanException("Error while installing config from %s\n%s" % (item, str(e)))
 
 
-def configuration_install(item, client_cache, output, verify_ssl, requester,
+def configuration_install(path_or_url, client_cache, output, verify_ssl, requester,
                           config_type=None, args=None):
+    if path_or_url is None:
+        try:
+            item = client_cache.conan_config.get_item("general.config_install")
+            _config_type, path_or_url, _args = _process_config_install_item(item)
+        except ConanException:
+            raise ConanException("Called config install without arguments and "
+                                 "'general.config_install' not defined in conan.conf")
+    else:
+        _config_type, path_or_url, _args = _process_config_install_item(path_or_url)
+
+    config_type = config_type or _config_type
+    args = args or _args
+
+    if os.path.exists(path_or_url):
+        path_or_url = os.path.abspath(path_or_url)
+
     tmp_folder = os.path.join(client_cache.conan_folder, "tmp_config_install")
     # necessary for Mac OSX, where the temp folders in /var/ are symlinks to /private/var/
     tmp_folder = os.path.realpath(tmp_folder)
     mkdir(tmp_folder)
     try:
-        if item is None:
-            try:
-                item = client_cache.conan_config.get_item("general.config_install")
-            except ConanException:
-                raise ConanException("Called config install without arguments and "
-                                     "'general.config_install' not defined in conan.conf")
-
-        if item.endswith(".git") or config_type == "git":
-            _process_git_repo(item, client_cache, output, tmp_folder, verify_ssl, args)
-        elif os.path.isdir(item):
-            _process_folder(item, client_cache, output)
-        elif os.path.isfile(item):
-            _process_zip_file(item, client_cache, output, tmp_folder)
-        elif item.startswith("http"):
-            _process_download(item, client_cache, output, tmp_folder, verify_ssl,
+
+        if config_type == "git":
+            _process_git_repo(path_or_url, client_cache, output, tmp_folder, verify_ssl, args)
+        elif config_type == "dir":
+            args = None
+            _process_folder(path_or_url, client_cache, output)
+        elif config_type == "file":
+            args = None
+            _process_zip_file(path_or_url, client_cache, output, tmp_folder)
+        elif config_type == "url":
+            args = None
+            _process_download(path_or_url, client_cache, output, tmp_folder, verify_ssl,
                               requester=requester)
         else:
-            raise ConanException("I don't know how to process %s" % item)
+            raise ConanException("Unable to process config install: %s" % path_or_url)
     finally:
-        if item:
-            client_cache.conan_config.set_item("general.config_install", item)
+        if config_type is not None and path_or_url is not None:
+            value = "%s:[%s, %s]" % (config_type, path_or_url, args)
+            client_cache.conan_config.set_item("general.config_install", value)
         rmdir(tmp_folder)
+
+
+def _process_config_install_item(item):
+    """
+    Processes a config_install item and outputs a tuple with the configuration type, the path/url
+    and additional args
+
+    :param item: config_install item value from conan.conf with a simple path/url or a string
+    following this pattern: "<config_type>:[<path_or_url>, <args>]"
+    :return: configuration source type (git, url, dir or file), path to file/dir or git/http url and
+    additional arguments
+    """
+    config_type, path_or_url, args = None, None, None
+    if not item.startswith(("git:", "dir:", "url:", "file:")):
+        path_or_url = item
+        if path_or_url.endswith(".git"):
+            config_type = "git"
+        elif os.path.isdir(path_or_url):
+            config_type = "dir"
+        elif os.path.isfile(path_or_url):
+            config_type = "file"
+        elif path_or_url.startswith("http"):
+            config_type = "url"
+        else:
+            raise ConanException("Unable to process config install: %s" % path_or_url)
+    else:
+        config_type, rest = item.split(":", 1)
+        path_or_url, args = [item.strip() for item in rest[1:-1].rstrip().split(",", 1)]
+        args = None if "none" in args.lower() else args
+    return config_type, path_or_url, args
diff --git a/conans/client/tools/apple.py b/conans/client/tools/apple.py
index ffd9a0b8a..c2c01d130 100644
--- a/conans/client/tools/apple.py
+++ b/conans/client/tools/apple.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-
+import os
 import subprocess
 
 
@@ -131,3 +131,14 @@ class XCRun(object):
     def libtool(self):
         """path to libtool"""
         return self.find('libtool')
+
+
+def apple_dot_clean(folder):
+    files = os.listdir(folder)
+    for f in files:
+        full_name = os.path.join(folder, f)
+        if os.path.isdir(full_name):
+            apple_dot_clean(full_name)
+        elif f.startswith("._"):
+            if os.path.exists(os.path.join(folder, f[2:])):
+                os.remove(full_name)
\ No newline at end of file
diff --git a/conans/client/tools/files.py b/conans/client/tools/files.py
index 0db495d0e..4201ae6c2 100644
--- a/conans/client/tools/files.py
+++ b/conans/client/tools/files.py
@@ -363,3 +363,5 @@ def unix2dos(filepath):
 
 def dos2unix(filepath):
     _replace_with_separator(filepath, "\n")
+
+
diff --git a/conans/client/tools/scm.py b/conans/client/tools/scm.py
index 740ac7bf8..b0f43f852 100644
--- a/conans/client/tools/scm.py
+++ b/conans/client/tools/scm.py
@@ -58,7 +58,7 @@ class Git(SCMBase):
         # TODO: This should be a context manager
         return self.run("config http.sslVerify %s" % ("true" if self._verify_ssl else "false"))
 
-    def clone(self, url, branch=None):
+    def clone(self, url, branch=None, args=""):
         url = self.get_url_with_credentials(url)
         if os.path.exists(url):
             url = url.replace("\\", "/")  # Windows local directory
@@ -75,7 +75,7 @@ class Git(SCMBase):
             output += self.run("checkout -t origin/%s" % branch)
         else:
             branch_cmd = "--branch %s" % branch if branch else ""
-            output = self.run('clone "%s" . %s' % (url, branch_cmd))
+            output = self.run('clone "%s" . %s %s' % (url, branch_cmd, args))
             output += self._configure_ssl_verify()
 
         return output
diff --git a/conans/client/tools/win.py b/conans/client/tools/win.py
index f424d04cf..824d8fb07 100644
--- a/conans/client/tools/win.py
+++ b/conans/client/tools/win.py
@@ -409,11 +409,11 @@ def vcvars_command(settings, arch=None, compiler_version=None, force=False, vcva
 
 
 def vcvars_dict(settings, arch=None, compiler_version=None, force=False, filter_known_paths=False,
-                vcvars_ver=None, winsdk_version=None, only_diff=True):
+                vcvars_ver=None, winsdk_version=None, only_diff=True, output=None):
     known_path_lists = ("include", "lib", "libpath", "path")
     cmd = vcvars_command(settings, arch=arch,
                          compiler_version=compiler_version, force=force,
-                         vcvars_ver=vcvars_ver, winsdk_version=winsdk_version)
+                         vcvars_ver=vcvars_ver, winsdk_version=winsdk_version, output=output)
     cmd += " && echo __BEGINS__ && set"
     ret = decode_text(subprocess.check_output(cmd, shell=True))
     new_env = {}
diff --git a/conans/tools.py b/conans/tools.py
index 3e76a8eb0..aafa5eca0 100644
--- a/conans/tools.py
+++ b/conans/tools.py
@@ -177,7 +177,6 @@ vs_installation_path = tools_win.vs_installation_path
 vswhere = tools_win.vswhere
 vs_comntools = tools_win.vs_comntools
 find_windows_10_sdk = tools_win.find_windows_10_sdk
-vcvars_dict = tools_win.vcvars_dict
 vcvars = tools_win.vcvars
 escape_windows_cmd = tools_win.escape_windows_cmd
 get_cased_path = tools_win.get_cased_path
@@ -198,6 +197,10 @@ def vcvars_command(*args, **kwargs):
     return tools_win.vcvars_command(output=_global_output, *args, **kwargs)
 
 
+def vcvars_dict(*args, **kwargs):
+    return tools_win.vcvars_dict(output=_global_output, *args, **kwargs)
+
+
 def latest_vs_version_installed(*args, **kwargs):
     return tools_win.latest_vs_version_installed(output=_global_output, *args, **kwargs)
 


Test patch:
diff --git a/conans/test/functional/command/config_install_test.py b/conans/test/functional/command/config_install_test.py
index f9a327ffb..febdad8c1 100644
--- a/conans/test/functional/command/config_install_test.py
+++ b/conans/test/functional/command/config_install_test.py
@@ -180,7 +180,7 @@ class Pkg(ConanFile):
         """
         zippath = self._create_zip()
         self.client.run('config install "%s"' % zippath)
-        self._check(zippath)
+        self._check("file:[%s, None]" % zippath)
         self.assertTrue(os.path.exists(zippath))
 
     def install_dir_test(self):
@@ -189,7 +189,7 @@ class Pkg(ConanFile):
         folder = self._create_profile_folder()
         self.assertTrue(os.path.isdir(folder))
         self.client.run('config install "%s"' % folder)
-        self._check(folder)
+        self._check("dir:[%s, None]" % folder)
 
     def test_without_profile_folder(self):
         shutil.rmtree(self.client.client_cache.profiles_path)
@@ -209,17 +209,17 @@ class Pkg(ConanFile):
 
         with patch.object(Downloader, 'download', new=my_download):
             self.client.run("config install http://myfakeurl.com/myconf.zip")
-            self._check("http://myfakeurl.com/myconf.zip")
+            self._check("url:[http://myfakeurl.com/myconf.zip, None]")
 
             # repeat the process to check
             self.client.run("config install http://myfakeurl.com/myconf.zip")
-            self._check("http://myfakeurl.com/myconf.zip")
+            self._check("url:[http://myfakeurl.com/myconf.zip, None]")
 
     def failed_install_repo_test(self):
         """ should install from a git repo
         """
         self.client.run('config install notexistingrepo.git', assert_error=True)
-        self.assertIn("ERROR: config install error. Can't clone repo", self.client.out)
+        self.assertIn("ERROR: Can't clone repo", self.client.out)
 
     def failed_install_http_test(self):
         """ should install from a http zip
@@ -241,7 +241,8 @@ class Pkg(ConanFile):
             self.client.runner('git commit -m "mymsg"')
 
         self.client.run('config install "%s/.git"' % folder)
-        self._check("%s/.git" % folder)
+        check_path = os.path.join(folder, ".git")
+        self._check("git:[%s, None]" % check_path)
 
     def install_repo_relative_test(self):
         relative_folder = "./config"
@@ -256,7 +257,7 @@ class Pkg(ConanFile):
             self.client.runner('git commit -m "mymsg"')
 
         self.client.run('config install "%s/.git"' % relative_folder)
-        self._check(os.path.join("%s" % folder, ".git"))
+        self._check("git:[%s, None]" % os.path.join("%s" % folder, ".git"))
 
     def install_custom_args_test(self):
         """ should install from a git repo
@@ -270,8 +271,9 @@ class Pkg(ConanFile):
             self.client.runner('git config user.email myname@mycompany.com')
             self.client.runner('git commit -m "mymsg"')
 
-        self.client.run('config install "%s/.git" --args="-c init.templateDir=\"\""' % folder)
-        self._check("%s/.git" % folder)
+        self.client.run('config install "%s/.git" --args="-c init.templateDir=value"' % folder)
+        check_path = os.path.join(folder, ".git")
+        self._check("git:[%s, -c init.templateDir=value]" % check_path)
 
     def force_git_type_test(self):
         client = TestClient()
@@ -284,7 +286,7 @@ class Pkg(ConanFile):
         zippath = self._create_zip()
         self.client.run('config set general.config_install="%s"' % zippath)
         self.client.run("config install")
-        self._check(zippath)
+        self._check("file:[%s, None]" % zippath)
 
     def reinstall_error_test(self):
         """ should use configured URL in conan.conf
@@ -341,7 +343,7 @@ class Pkg(ConanFile):
             self.assertIn(fake_url_hidden_password, self.client.out)
 
             # Check credentials still stored in configuration
-            self._check(fake_url_with_credentials)
+            self._check("url:[%s, None]" % fake_url_with_credentials)
 
     def ssl_verify_test(self):
         fake_url = "https://fakeurl.com/myconf.zip"
@@ -359,3 +361,49 @@ class Pkg(ConanFile):
 
         with patch.object(Downloader, 'download', new=download_verify_true):
             self.client.run("config install %s --verify-ssl=True" % fake_url)
+
+    def test_git_checkout_is_possible(self):
+        folder = self._create_profile_folder()
+        with tools.chdir(folder):
+            self.client.runner('git init .')
+            self.client.runner('git add .')
+            self.client.runner('git config user.name myname')
+            self.client.runner('git config user.email myname@mycompany.com')
+            self.client.runner('git commit -m "mymsg"')
+            self.client.runner('git checkout -b other_branch')
+            save(os.path.join(folder, "hooks", "cust", "cust.py"), "")
+            self.client.runner('git add .')
+            self.client.runner('git commit -m "my file"')
+            self.client.runner('git tag 0.0.1')
+            self.client.runner('git checkout master')
+
+        # Without checkout
+        self.client.run('config install "%s/.git"' % folder)
+        check_path = os.path.join(folder, ".git")
+        self._check("git:[%s, None]" % check_path)
+        file_path = os.path.join(self.client.client_cache.conan_folder, "hooks", "cust", "cust.py")
+        self.assertFalse(os.path.exists(file_path))
+        # With checkout tag and reuse url
+        self.client.run('config install --args="-b 0.0.1"')
+        check_path = os.path.join(folder, ".git")
+        self._check("git:[%s, -b 0.0.1]" % check_path)
+        self.assertTrue(os.path.exists(file_path))
+        # With checkout branch and reuse url
+        self.client.run('config install --args="-b other_branch"')
+        check_path = os.path.join(folder, ".git")
+        self._check("git:[%s, -b other_branch]" % check_path)
+        self.assertTrue(os.path.exists(file_path))
+        # Add changes to that branch and update
+        with tools.chdir(folder):
+            self.client.runner('git checkout other_branch')
+            save(os.path.join(folder, "hooks", "other", "other.py"), "")
+            self.client.runner('git add .')
+            self.client.runner('git commit -m "my other file"')
+            self.client.runner('git checkout master')
+        other_path = os.path.join(self.client.client_cache.conan_folder, "hooks", "other",
+                                  "other.py")
+        self.assertFalse(os.path.exists(other_path))
+        self.client.run('config install')
+        check_path = os.path.join(folder, ".git")
+        self._check("git:[%s, -b other_branch]" % check_path)
+        self.assertTrue(os.path.exists(other_path))
diff --git a/conans/test/functional/command/export_test.py b/conans/test/functional/command/export_test.py
index d69b9b859..90da986de 100644
--- a/conans/test/functional/command/export_test.py
+++ b/conans/test/functional/command/export_test.py
@@ -338,8 +338,8 @@ class OpenSSLConan(ConanFile):
     def test_export_the_same_code(self):
         file_list = self._create_packages_and_builds()
         # Export the same conans
-
-        conan2 = TestClient(self.conan.base_folder)
+        # Do not adjust cpu_count, it is reusing a cache
+        conan2 = TestClient(self.conan.base_folder, cpu_count=False)
         files2 = cpp_hello_conan_files("Hello0", "0.1")
         conan2.save(files2)
         conan2.run("export . lasote/stable")
@@ -373,7 +373,8 @@ class OpenSSLConan(ConanFile):
         self._create_packages_and_builds()
         # Export an update of the same conans
 
-        conan2 = TestClient(self.conan.base_folder)
+        # Do not adjust cpu_count, it is reusing a cache
+        conan2 = TestClient(self.conan.base_folder, cpu_count=False)
         files2 = cpp_hello_conan_files("Hello0", "0.1")
         files2[CONANFILE] = "# insert comment\n %s" % files2[CONANFILE]
         conan2.save(files2)
diff --git a/conans/test/functional/command/install_test.py b/conans/test/functional/command/install_test.py
index 83c72b0aa..0d8c0aff6 100644
--- a/conans/test/functional/command/install_test.py
+++ b/conans/test/functional/command/install_test.py
@@ -312,7 +312,8 @@ class Pkg(ConanFile):
     def change_option_txt_test(self):
         self._create("Hello0", "0.1")
 
-        client = TestClient(base_folder=self.client.base_folder)
+        # Do not adjust cpu_count, it is reusing a cache
+        client = TestClient(base_folder=self.client.base_folder, cpu_count=False)
         files = {CONANFILE_TXT: """[requires]
         Hello0/0.1@lasote/stable
 
diff --git a/conans/test/functional/old/conanfile_extend_test.py b/conans/test/functional/old/conanfile_extend_test.py
index be03a75bb..f93e7218b 100644
--- a/conans/test/functional/old/conanfile_extend_test.py
+++ b/conans/test/functional/old/conanfile_extend_test.py
@@ -69,7 +69,8 @@ class DevConanFile(HelloConan2):
         files = {"conanfile.py": base,
                  "conanfile_dev.py": extension}
 
-        client = TestClient(self.base_folder)
+        # Do not adjust cpu_count, it is reusing a cache
+        client = TestClient(self.base_folder, cpu_count=False)
         client.save(files)
         client.run("install . --build")
         conaninfo = load(os.path.join(client.current_folder, "conaninfo.txt"))
@@ -127,7 +128,8 @@ class ConanFileToolsTest(ConanBase):
     '''
         files = {"base_conan.py": base,
                  "conanfile.py": extension}
-        client = TestClient(self.base_folder)
+        # Do not adjust cpu_count, it is reusing a cache
+        client = TestClient(self.base_folder, cpu_count=False)
 
         client.save(files)
         client.run("create . conan/testing -o test:test_option=3 --build")
@@ -155,7 +157,8 @@ otherlib:otherlib_option = 1
         files = {"conanfile.txt": base,
                  "conanfile_dev.txt": extension}
 
-        client = TestClient(self.base_folder)
+        # Do not adjust cpu_count, it is reusing a cache
+        client = TestClient(self.base_folder, cpu_count=False)
         client.save(files)
         client.run("install . --build")
         conaninfo = load(os.path.join(client.current_folder, "conaninfo.txt"))
diff --git a/conans/test/functional/old/scm_test.py b/conans/test/functional/old/scm_test.py
index e32a0ef97..1787da433 100644
--- a/conans/test/functional/old/scm_test.py
+++ b/conans/test/functional/old/scm_test.py
@@ -88,7 +88,7 @@ class ConanLib(ConanFile):
         conanfile = conanfile.replace('"onesubfolder"', '"othersubfolder"')
         self.client.save({"conanfile.py": conanfile})
         self.client.run("create . user/channel")
-        folder = self.client.client_cache.source(ConanFileReference.loads("lib/0.1@user/channel"))
+        folder = self.client.client_cache.source(self.reference)
         self.assertIn("othersubfolder", os.listdir(folder))
         self.assertTrue(os.path.exists(os.path.join(folder, "othersubfolder", "myfile")))
 
@@ -105,7 +105,7 @@ class ConanLib(ConanFile):
         self.client.runner('git clone "%s" .' % repo, cwd=self.client.current_folder)
         self.client.run("export . user/channel")
         self.assertIn("WARN: Repo origin looks like a local path", self.client.out)
-        os.remove(self.client.client_cache.scm_folder(ConanFileReference.loads("lib/0.1@user/channel")))
+        os.remove(self.client.client_cache.scm_folder(self.reference))
         self.client.run("install lib/0.1@user/channel --build")
         self.assertIn("lib/0.1@user/channel: Getting sources from url:", self.client.out)
 
@@ -178,7 +178,7 @@ class ConanLib(ConanFile):
         self.assertEquals(load(sources_dir), curdir.replace('\\', '/'))  # Root of git is 'curdir'
 
     def test_deleted_source_folder(self):
-        path, commit = create_local_git_repo({"myfile": "contents"}, branch="my_release")
+        path, _ = create_local_git_repo({"myfile": "contents"}, branch="my_release")
         curdir = self.client.current_folder.replace("\\", "/")
         conanfile = base_git.format(url="auto", revision="auto")
         self.client.save({"conanfile.py": conanfile, "myfile.txt": "My file is copied"})
@@ -196,18 +196,18 @@ class ConanLib(ConanFile):
     def test_excluded_repo_fies(self):
         conanfile = base_git.format(url="auto", revision="auto")
         conanfile = conanfile.replace("short_paths = True", "short_paths = False")
-        path, commit = create_local_git_repo({"myfile": "contents",
-                                              "ignored.pyc": "bin",
-                                              ".gitignore": """
+        path, _ = create_local_git_repo({"myfile": "contents",
+                                         "ignored.pyc": "bin",
+                                         ".gitignore": """
 *.pyc
 my_excluded_folder
 other_folder/excluded_subfolder
 """,
-                                              "myfile.txt": "My file!",
-                                              "my_excluded_folder/some_file": "hey Apple!",
-                                              "other_folder/excluded_subfolder/some_file": "hey Apple!",
-                                              "other_folder/valid_file": "!",
-                                              "conanfile.py": conanfile}, branch="my_release")
+                                         "myfile.txt": "My file!",
+                                         "my_excluded_folder/some_file": "hey Apple!",
+                                         "other_folder/excluded_subfolder/some_file": "hey Apple!",
+                                         "other_folder/valid_file": "!",
+                                         "conanfile.py": conanfile}, branch="my_release")
         self.client.current_folder = path
         self.client.runner('git remote add origin "%s"' % path.replace("\\", "/"), cwd=path)
         self.client.run("create . user/channel")
@@ -311,8 +311,8 @@ class ConanLib(ConanFile):
         self.output.warn("Contents: %s" % contents)
 
 '''
-        path, commit = create_local_git_repo({"myfile": "contents", "conanfile.py": conanfile},
-                                             branch="my_release")
+        path, _ = create_local_git_repo({"myfile": "contents", "conanfile.py": conanfile},
+                                        branch="my_release")
         self.client.current_folder = path
         self.client.runner('git remote add origin https://myrepo.com.git', cwd=path)
         self.client.run("create . lib/1.0@user/channel")
@@ -326,7 +326,8 @@ class ConanLib(ConanFile):
     def test_submodule(self):
         subsubmodule, _ = create_local_git_repo({"subsubmodule": "contents"})
         submodule, _ = create_local_git_repo({"submodule": "contents"}, submodules=[subsubmodule])
-        path, commit = create_local_git_repo({"myfile": "contents"}, branch="my_release", submodules=[submodule])
+        path, commit = create_local_git_repo({"myfile": "contents"}, branch="my_release",
+                                             submodules=[submodule])
 
         def _relative_paths(folder):
             submodule_path = os.path.join(
@@ -421,16 +422,16 @@ class ConanLib(ConanFile):
         "url": "auto",
         "revision": "auto"
     }
-    
+
     def build(self):
         pass
 '''
         self.client.current_folder = path
         self.client.save({"conanfile.py": conanfile})
         self.client.run("create . user/channel")
-        
+
     def test_source_method_export_sources_and_scm_mixed(self):
-        path, commit = create_local_git_repo({"myfile": "contents"}, branch="my_release")
+        path, _ = create_local_git_repo({"myfile": "contents"}, branch="my_release")
 
         conanfile = '''
 import os
@@ -473,6 +474,77 @@ class ConanLib(ConanFile):
         data2 = json.loads(the_json)
         self.assertEquals(data, data2)
 
+    def test_git_delegated_function(self):
+        conanfile = """
+import os
+from conans import ConanFile
+from conans.client.tools.scm import Git
+
+def get_revision():
+    here = os.path.dirname(__file__)
+    git = Git(here)
+    return git.get_commit()
+
+def get_url():
+    def nested_url():
+        here = os.path.dirname(__file__)
+        git = Git(here)
+        return git.get_remote_url()
+    return nested_url()
+
+class MyLib(ConanFile):
+    name = "issue"
+    version = "3831"
+    scm = {'type': 'git', 'url': get_url(), 'revision': get_revision()}
+
+"""
+        self.client.save({"conanfile.py": conanfile})
+        path, commit = create_local_git_repo(folder=self.client.current_folder)
+        self.client.runner('git remote add origin "%s"' % path.replace("\\", "/"), cwd=path)
+
+        self.client.run("export . user/channel")
+        reference = ConanFileReference.loads("issue/3831@user/channel")
+        exported_conanfile = self.client.client_cache.conanfile(reference)
+        content = load(exported_conanfile)
+        self.assertIn(commit, content)
+
+    def test_delegated_python_code(self):
+        client = TestClient()
+        code_file = """
+from conans.tools import Git
+from conans import ConanFile
+
+def get_commit(repo_path):
+    git = Git(repo_path)
+    return git.get_commit()
+
+class MyLib(ConanFile):
+    pass
+"""
+        client.save({"conanfile.py": code_file})
+        client.run("export . tool/0.1@user/testing")
+
+        conanfile = """import os
+from conans import ConanFile, python_requires
+from conans.tools import load
+tool = python_requires("tool/0.1@user/testing")
+
+class MyLib(ConanFile):
+    scm = {'type': 'git', 'url': '%s', 'revision': tool.get_commit(os.path.dirname(__file__))}
+    def build(self):
+        self.output.info("File: {}".format(load("file.txt")))
+""" % client.current_folder.replace("\\", "/")
+
+        client.save({"conanfile.py": conanfile, "file.txt": "hello!"})
+        path, commit = create_local_git_repo(folder=client.current_folder)
+        client.runner('git remote add origin "%s"' % path.replace("\\", "/"), cwd=path)
+
+        client.run("export . pkg/0.1@user/channel")
+        reference = ConanFileReference.loads("pkg/0.1@user/channel")
+        exported_conanfile = client.client_cache.conanfile(reference)
+        content = load(exported_conanfile)
+        self.assertIn(commit, content)
+
 
 @attr('svn')
 class SVNSCMTest(SVNLocalRepoTestCase):
@@ -531,9 +603,11 @@ class ConanLib(ConanFile):
 
     def test_auto_svn(self):
         conanfile = base_svn.format(directory="None", url="auto", revision="auto")
-        project_url, rev = self.create_project(files={"conanfile.py": conanfile, "myfile.txt": "My file is copied"})
+        project_url, _ = self.create_project(files={"conanfile.py": conanfile,
+                                                    "myfile.txt": "My file is copied"})
         project_url = project_url.replace(" ", "%20")
-        self.client.runner('svn co "{url}" "{path}"'.format(url=project_url, path=self.client.current_folder))
+        self.client.runner('svn co "{url}" "{path}"'.format(url=project_url,
+                                                            path=self.client.current_folder))
 
         curdir = self.client.current_folder.replace("\\", "/")
         # Create the package, will copy the sources from the local folder
@@ -548,7 +622,8 @@ class ConanLib(ConanFile):
 
         # Export again but now with absolute reference, so no pointer file is created nor kept
         svn = SVN(curdir)
-        self.client.save({"conanfile.py": base_svn.format(url=svn.get_remote_url(), revision=svn.get_revision())})
+        self.client.save({"conanfile.py": base_svn.format(url=svn.get_remote_url(),
+                                                          revision=svn.get_revision())})
         self.client.run("create . user/channel")
         sources_dir = self.client.client_cache.scm_folder(self.reference)
         self.assertFalse(os.path.exists(sources_dir))
@@ -565,9 +640,11 @@ class ConanLib(ConanFile):
         conanfile = conanfile.replace("short_paths = True", "short_paths = False")
         conanfile = conanfile.format(directory="None", url="auto", revision="auto")
 
-        project_url, rev = self.create_project(files={"conanfile.py": conanfile, "myfile.txt": "My file is copied"})
+        project_url, _ = self.create_project(files={"conanfile.py": conanfile,
+                                                    "myfile.txt": "My file is copied"})
         project_url = project_url.replace(" ", "%20")
-        self.client.runner('svn co "{url}" "{path}"'.format(url=project_url, path=self.client.current_folder))
+        self.client.runner('svn co "{url}" "{path}"'.format(url=project_url,
+                                                            path=self.client.current_folder))
         self.client.run("create . user/channel")
 
         folder = self.client.client_cache.source(ConanFileReference.loads("lib/0.1@user/channel"))
@@ -580,8 +657,8 @@ class ConanLib(ConanFile):
         """
         curdir = self.client.current_folder
         conanfile = base_svn.format(url="auto", revision="auto")
-        project_url, rev = self.create_project(files={"conan/conanfile.py": conanfile,
-                                                      "myfile.txt": "My file is copied"})
+        project_url, _ = self.create_project(files={"conan/conanfile.py": conanfile,
+                                                    "myfile.txt": "My file is copied"})
         self.client.runner('svn co "{url}" "{path}"'.format(url=project_url,
                                                             path=self.client.current_folder))
         self.client.run("create conan/ user/channel")
@@ -596,13 +673,16 @@ class ConanLib(ConanFile):
     def test_excluded_repo_fies(self):
         conanfile = base_svn.format(url="auto", revision="auto")
         conanfile = conanfile.replace("short_paths = True", "short_paths = False")
-        project_url, rev = self.create_project(files={"myfile": "contents",
-                                                      "ignored.pyc": "bin",  # SVN ignores pyc files by default: http://blogs.collab.net/subversion/repository-dictated-configuration-day-3-global-ignores
-                                                      # ".gitignore": "*.pyc\n",
-                                                      "myfile.txt": "My file!",
-                                                      "conanfile.py": conanfile})
+        # SVN ignores pyc files by default:
+        # http://blogs.collab.net/subversion/repository-dictated-configuration-day-3-global-ignores
+        project_url, _ = self.create_project(files={"myfile": "contents",
+                                                    "ignored.pyc": "bin",
+                                                    # ".gitignore": "*.pyc\n",
+                                                    "myfile.txt": "My file!",
+                                                    "conanfile.py": conanfile})
         project_url = project_url.replace(" ", "%20")
-        self.client.runner('svn co "{url}" "{path}"'.format(url=project_url, path=self.client.current_folder))
+        self.client.runner('svn co "{url}" "{path}"'.format(url=project_url,
+                                                            path=self.client.current_folder))
 
         self.client.run("create . user/channel")
         self.assertIn("Copying sources to build folder", self.client.out)
@@ -621,9 +701,11 @@ class ConanLib(ConanFile):
     def source(self):
         self.output.warn("SOURCE METHOD CALLED")
 """
-        project_url, rev = self.create_project(files={"conanfile.py": conanfile, "myfile.txt": "My file is copied"})
+        project_url, _ = self.create_project(files={"conanfile.py": conanfile, "myfile.txt":
+                                                    "My file is copied"})
         project_url = project_url.replace(" ", "%20")
-        self.client.runner('svn co "{url}" "{path}"'.format(url=project_url, path=self.client.current_folder))
+        self.client.runner('svn co "{url}" "{path}"'.format(url=project_url,
+                                                            path=self.client.current_folder))
         self.client.save({"aditional_file.txt": "contents"})
 
         self.client.run("source . --source-folder=./source")
@@ -661,9 +743,11 @@ class ConanLib(ConanFile):
     def source(self):
         self.output.warn("SOURCE METHOD CALLED")
 """
-        project_url, rev = self.create_project(files={"conanfile.py": conanfile, "myfile.txt": "My file is copied"})
+        project_url, _ = self.create_project(files={"conanfile.py": conanfile,
+                                                    "myfile.txt": "My file is copied"})
         project_url = project_url.replace(" ", "%20")
-        self.client.runner('svn co "{url}" "{path}"'.format(url=project_url, path=self.client.current_folder))
+        self.client.runner('svn co "{url}" "{path}"'.format(url=project_url,
+                                                            path=self.client.current_folder))
 
         self.client.run("source . --source-folder=./source")
         self.assertFalse(os.path.exists(os.path.join(curdir, "source", "myfile.txt")))
@@ -676,9 +760,11 @@ class ConanLib(ConanFile):
         self.client = TestClient(servers=self.servers, users={"myremote": [("lasote", "mypass")]})
 
         conanfile = base_svn.format(url="auto", revision="auto")
-        project_url, rev = self.create_project(files={"conanfile.py": conanfile, "myfile.txt": "My file is copied"})
+        project_url, _ = self.create_project(files={"conanfile.py": conanfile,
+                                                    "myfile.txt": "My file is copied"})
         project_url = project_url.replace(" ", "%20")
-        self.client.runner('svn co "{url}" "{path}"'.format(url=project_url, path=self.client.current_folder))
+        self.client.runner('svn co "{url}" "{path}"'.format(url=project_url,
+                                                            path=self.client.current_folder))
         self.client.run("export . lasote/channel")
         self.client.run("upload lib* -c")
 
@@ -703,9 +789,10 @@ class ConanLib(ConanFile):
         self.output.warn("Contents: %s" % contents)
 
 '''
-        project_url, rev = self.create_project(files={"myfile": "contents", "conanfile.py": conanfile})
+        project_url, _ = self.create_project(files={"myfile": "contents", "conanfile.py": conanfile})
         project_url = project_url.replace(" ", "%20")
-        self.client.runner('svn co "{url}" "{path}"'.format(url=project_url, path=self.client.current_folder))
+        self.client.runner('svn co "{url}" "{path}"'.format(url=project_url,
+                                                            path=self.client.current_folder))
 
         self.client.run("create . lib/1.0@user/channel")
         self.assertIn("Contents: contents", self.client.out)
@@ -722,7 +809,8 @@ class ConanLib(ConanFile):
     def test_source_method_export_sources_and_scm_mixed(self):
         project_url, rev = self.create_project(files={"myfile": "contents"})
         project_url = project_url.replace(" ", "%20")
-        self.client.runner('svn co "{url}" "{path}"'.format(url=project_url, path=self.client.current_folder))
+        self.client.runner('svn co "{url}" "{path}"'.format(url=project_url,
+                                                            path=self.client.current_folder))
 
         conanfile = '''
 import os
diff --git a/conans/test/functional/old/test_migrations.py b/conans/test/functional/old/test_migrations.py
index 596ea5c2d..b48e0edd6 100644
--- a/conans/test/functional/old/test_migrations.py
+++ b/conans/test/functional/old/test_migrations.py
@@ -81,7 +81,8 @@ the old general
                                                         "attribute_checker.py")
             save(old_conf_path, "\n[plugins]    # CONAN_PLUGINS\nattribute_checker")
             save(old_attribute_checker_plugin, "")
-            client_cache = TestClient(base_folder=old_user_home).client_cache
+            # Do not adjust cpu_count, it is reusing a cache
+            client_cache = TestClient(base_folder=old_user_home, cpu_count=False).client_cache
             assert old_conan_folder == client_cache.conan_folder
             return old_user_home, old_conan_folder, old_conf_path, old_attribute_checker_plugin,\
                    client_cache
diff --git a/conans/test/functional/tgz_macos_dot_files.py b/conans/test/functional/tgz_macos_dot_files.py
new file mode 100644
index 000000000..1f4d46ee8
--- /dev/null
+++ b/conans/test/functional/tgz_macos_dot_files.py
@@ -0,0 +1,103 @@
+import os
+import platform
+import shutil
+import subprocess
+import tempfile
+import textwrap
+import unittest
+
+from conans.client.remote_manager import uncompress_file
+from conans.model.ref import ConanFileReference, PackageReference
+from conans.paths import EXPORT_SOURCES_TGZ_NAME
+from conans.test.utils.tools import TestBufferConanOutput
+from conans.test.utils.tools import TestClient, TestServer
+
+
+@unittest.skipUnless(platform.system() == "Darwin", "Requires OSX")
+class TgzMacosDotFilesTest(unittest.TestCase):
+
+    def _add_macos_metadata_to_file(self, filepath):
+        subprocess.call(["xattr", "-w", "name", "value", filepath])
+
+    def _test_for_metadata_in_zip_file(self, tgz, annotated_file, dot_file_expected):
+        tmp_folder = tempfile.mkdtemp()
+        try:
+            uncompress_file(src_path=tgz,
+                            dest_folder=tmp_folder,
+                            output=TestBufferConanOutput())
+            self.assertTrue(os.path.exists(os.path.join(tmp_folder, annotated_file)))
+            self.assertEqual(dot_file_expected,
+                             os.path.exists(os.path.join(tmp_folder, "._" + annotated_file)))
+        finally:
+            shutil.rmtree(tmp_folder)
+
+    def _test_for_metadata(self, folder, annotated_file, dot_file_expected):
+        """ We want to check if the file has metadata associated: Mac creates the
+            ._ files at the moment of creating a tar file in order to send the
+            metadata associated to every file. """
+        self.assertTrue(os.path.exists(os.path.join(folder, annotated_file)))
+
+        tmp_folder = tempfile.mkdtemp()
+        try:
+            tgz = os.path.join(tmp_folder, 'compressed.tgz')
+            subprocess.call(["tar", "-zcvf", tgz, "-C", folder, "."])
+            self._test_for_metadata_in_zip_file(tgz, annotated_file, dot_file_expected)
+        finally:
+            shutil.rmtree(tmp_folder)
+
+    def test_dot_files(self):
+        """ Check behavior related to ._ files in Macos OS
+
+            Macos has the ability to store metadata associated to files. This metadata can be
+            stored in the HFS+ (Apple native) or Unix/UFS volumes, but if the store does
+            not have this capability it will be placed in a ._ file. So these files will
+            automatically be created by the OS when it is creating a package in order to
+            send this information.
+
+            Nevertheless, Conan is using the Python libraries to copy, tar and untar files
+            and this metadata is lost when using them. It can avoid some problems like
+            #3529 but there is missing information that can be valuable at some point in time.
+
+            This test is here just to be sure that the behavior is not changed without
+            noticing.
+            """
+
+        conanfile = textwrap.dedent("""\
+            from conans import ConanFile
+            
+            class Lib(ConanFile):
+                name = "lib"
+                version = "version"
+                exports_sources = "file.txt"
+                
+                def package(self):
+                    self.copy("file.txt")
+            """)
+
+        default_server = TestServer()
+        servers = {"default": default_server}
+        t = TestClient(path_with_spaces=False, servers=servers,
+                       users={"default": [("lasote", "mypass")]})
+        t.save(files={'conanfile.py': conanfile, 'file.txt': "content"})
+        self._add_macos_metadata_to_file(os.path.join(t.current_folder, 'file.txt'))
+        t.run("create . lasote/channel")
+
+        # Check if the metadata travels through the Conan commands
+        pkg_ref = PackageReference.loads(
+            "lib/version@lasote/channel:5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9")
+        pkg_folder = t.client_cache.package(pkg_ref)
+
+        # 1) When copied to the package folder, the metadata is lost
+        self._test_for_metadata(pkg_folder, 'file.txt', dot_file_expected=False)
+
+        # 2) If we add metadata to a file, it will be there
+        self._add_macos_metadata_to_file(os.path.join(pkg_folder, 'file.txt'))
+        self._test_for_metadata(pkg_folder, 'file.txt', dot_file_expected=True)
+
+        # 3) In the upload process, the metadata is lost again
+        ref = ConanFileReference.loads("lib/version@lasote/channel")
+        export_folder = t.client_cache.export(ref)
+        tgz = os.path.join(export_folder, EXPORT_SOURCES_TGZ_NAME)
+        self.assertFalse(os.path.exists(tgz))
+        t.run("upload lib/version@lasote/channel")
+        self._test_for_metadata_in_zip_file(tgz, 'file.txt', dot_file_expected=False)
diff --git a/conans/test/integration/basic_build_test.py b/conans/test/integration/basic_build_test.py
index 8881faebc..6878df086 100644
--- a/conans/test/integration/basic_build_test.py
+++ b/conans/test/integration/basic_build_test.py
@@ -32,7 +32,7 @@ class BasicBuildTest(unittest.TestCase):
 
 
 def build(tester, cmd, static, pure_c, use_cmake, lang):
-    client = TestClient(cpu_count=1)
+    client = TestClient()
     dll_export = client.default_compiler_visual_studio and not static
     files = cpp_hello_conan_files("Hello0", "0.1", dll_export=dll_export,
                                   pure_c=pure_c, use_cmake=use_cmake)
diff --git a/conans/test/integration/diamond_test.py b/conans/test/integration/diamond_test.py
index 82eac7ad4..0cd602479 100644
--- a/conans/test/integration/diamond_test.py
+++ b/conans/test/integration/diamond_test.py
@@ -20,18 +20,16 @@ class DiamondTest(unittest.TestCase):
         self.servers = {"default": test_server}
 
     def diamond_cmake_test(self):
-        self.client = TestClient(servers=self.servers, users={"default": [("lasote", "mypass")]},
-                                 cpu_count=1)
+        self.client = TestClient(servers=self.servers, users={"default": [("lasote", "mypass")]})
         self._run(use_cmake=True, language=1)
 
     def diamond_cmake_targets_test(self):
-        self.client = TestClient(servers=self.servers, users={"default": [("lasote", "mypass")]},
-                                 cpu_count=1)
+        self.client = TestClient(servers=self.servers, users={"default": [("lasote", "mypass")]})
         self._run(use_cmake=True, cmake_targets=True)
 
     def diamond_default_test(self):
         self.client = TestClient(servers=self.servers, users={"default": [("lasote", "mypass")]},
-                                 path_with_spaces=False, cpu_count=1)
+                                 path_with_spaces=False)
         self._run(use_cmake=False)
 
     def _export(self, name, version=None, deps=None, use_cmake=True, cmake_targets=False):
diff --git a/conans/test/integration/manifest_validation_test.py b/conans/test/integration/manifest_validation_test.py
index 12b55280c..d958c3c4a 100644
--- a/conans/test/integration/manifest_validation_test.py
+++ b/conans/test/integration/manifest_validation_test.py
@@ -126,7 +126,8 @@ class ConanFileTest(ConanFile):
     name = "Hello2"
     version = "0.1"
 """
-        client = TestClient(base_folder=self.client.base_folder)
+        # Do not adjust cpu_count, it is reusing a cache
+        client = TestClient(base_folder=self.client.base_folder, cpu_count=False)
         client.save({CONANFILE: conanfile})
         client.run("export . lasote/stable")
 
diff --git a/conans/test/unittests/client/conf/__init__.py b/conans/test/unittests/client/conf/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/conans/test/unittests/client/conf/config_installer.py b/conans/test/unittests/client/conf/config_installer.py
new file mode 100644
index 000000000..de62ed915
--- /dev/null
+++ b/conans/test/unittests/client/conf/config_installer.py
@@ -0,0 +1,57 @@
+import os
+import unittest
+from parameterized import parameterized
+
+from conans.client.conf.config_installer import _process_config_install_item
+from conans.errors import ConanException
+from conans.test.utils.test_files import temp_folder
+from conans.util.files import save
+
+
+class ConfigInstallerTests(unittest.TestCase):
+
+    def process_config_install_item_test(self):
+        config_type, url_or_path, args = _process_config_install_item(
+                "git:[whaterver.url.com/repo.git, --recusrive --other -b 0.3.4]")
+        self.assertEqual("git", config_type)
+        self.assertEqual("whaterver.url.com/repo.git", url_or_path)
+        self.assertEqual("--recusrive --other -b 0.3.4", args)
+
+        config_type, url_or_path, args = _process_config_install_item("whaterver.url.com/repo.git")
+        self.assertEqual("git", config_type)
+        self.assertEqual("whaterver.url.com/repo.git", url_or_path)
+        self.assertIsNone(args)
+
+        dir_path = temp_folder()
+        for dir_item in ["dir:[%s, None]" % dir_path, dir_path]:
+            config_type, url_or_path, args = _process_config_install_item(dir_item)
+            self.assertEqual("dir", config_type)
+            self.assertEqual(dir_path, url_or_path)
+            self.assertIsNone(args)
+
+        file_path = os.path.join(dir_path, "file.zip")
+        save(file_path, "")
+        for file_item in ["file:[%s, None]" % file_path, file_path]:
+            config_type, url_or_path, args = _process_config_install_item(file_item)
+            self.assertEqual("file", config_type)
+            self.assertEqual(file_path, url_or_path)
+            self.assertIsNone(args)
+
+        for url_item in ["url:[http://is/an/absloute/path with spaces/here/file.zip, None]",
+                         "http://is/an/absloute/path with spaces/here/file.zip"]:
+            config_type, url_or_path, args = _process_config_install_item(url_item)
+            self.assertEqual("url", config_type)
+            self.assertEqual("http://is/an/absloute/path with spaces/here/file.zip", url_or_path)
+            self.assertIsNone(args)
+
+        config_type, url, args = _process_config_install_item(
+                "url:[http://is/an/absloute/path with spaces/here/file.zip, --option]")
+        self.assertEqual("url", config_type)
+        self.assertEqual("http://is/an/absloute/path with spaces/here/file.zip", url)
+        self.assertEqual("--option", args)
+
+        # Test wrong input
+        for item in ["git@github.com:conan-io/conan.git, None"
+                     "file/not/exists.zip"]:
+            with self.assertRaisesRegex(ConanException, "Unable to process config install"):
+                _, _, _ = _process_config_install_item(item)
diff --git a/conans/test/unittests/client/tools/test_files.py b/conans/test/unittests/client/tools/test_files.py
new file mode 100644
index 000000000..6749c5ce7
--- /dev/null
+++ b/conans/test/unittests/client/tools/test_files.py
@@ -0,0 +1,44 @@
+# coding=utf-8
+
+import os
+import unittest
+
+from conans.client.tools.files import save, chdir
+from conans.client.tools import apple_dot_clean
+from conans.test.utils.test_files import temp_folder
+
+
+class DotCleanTest(unittest.TestCase):
+
+    def _run_test(self, tuples):
+        tmp_folder = temp_folder()
+        with chdir(tmp_folder):
+            for f, _ in tuples:
+                save(f, "")
+
+            apple_dot_clean(".")
+
+            for f, expected_after in tuples:
+                self.assertEqual(expected_after, os.path.exists(f))
+
+    def test_removal_normal(self):
+        files = [("file.txt", True),
+                 ("._file.txt", False),
+                 ("folder/file.txt", True),
+                 ("folder/._file.txt", False),]
+        self._run_test(tuples=files)
+
+    def test_only_remove_matching_ones(self):
+        files = [("file.txt", True),
+                 ("._file.txt", False),
+                 ("._other.txt", True)]
+        self._run_test(tuples=files)
+
+    def test_handle_dirs(self):
+        files = [("folder/file.txt", True),
+                 ("folder/._file.txt", False),
+                 ("._folder/file.txt", True),
+                 ("._folder/._file.txt", False),
+                 ("._other/._file.txt", True),
+                 ("._other2/file.txt", True)]
+        self._run_test(tuples=files)
diff --git a/conans/test/unittests/util/tools_test.py b/conans/test/unittests/util/tools_test.py
index bbe753299..c3d4b7d0a 100644
--- a/conans/test/unittests/util/tools_test.py
+++ b/conans/test/unittests/util/tools_test.py
@@ -970,7 +970,8 @@ ProgramFiles(x86)=C:\Program Files (x86)
        
 """.encode("utf-8")
 
-        def vcvars_command_mock(settings, arch, compiler_version, force, vcvars_ver, winsdk_version):  # @UnusedVariable
+        def vcvars_command_mock(settings, arch, compiler_version, force, vcvars_ver, winsdk_version,
+                                output):  # @UnusedVariable
             return "unused command"
 
         def subprocess_check_output_mock(cmd, shell):
diff --git a/conans/test/utils/tools.py b/conans/test/utils/tools.py
index 5eaa71002..d1339e0ae 100644
--- a/conans/test/utils/tools.py
+++ b/conans/test/utils/tools.py
@@ -32,10 +32,10 @@ from conans.client.conf import MIN_SERVER_COMPATIBLE_VERSION
 from conans.client.hook_manager import HookManager
 from conans.client.loader import ProcessedProfile
 from conans.client.output import ConanOutput
-from conans.client.remote_registry import RemoteRegistry, dump_registry
+from conans.client.remote_registry import dump_registry
 from conans.client.rest.conan_requester import ConanRequester
 from conans.client.rest.uploader_downloader import IterableToFileAdapter
-from conans.client.tools import replace_in_file
+from conans.client.tools.files import replace_in_file
 from conans.client.tools.files import chdir
 from conans.client.tools.scm import Git, SVN
 from conans.client.tools.win import get_cased_path
@@ -447,7 +447,7 @@ class TestClient(object):
                  servers=None, users=None, client_version=CLIENT_VERSION,
                  min_server_compatible_version=MIN_SERVER_COMPATIBLE_VERSION,
                  requester_class=None, runner=None, path_with_spaces=True,
-                 block_v2=None, revisions=None, cpu_count=None):
+                 block_v2=None, revisions=None, cpu_count=1):
         """
         storage_folder: Local storage path
         current_folder: Current execution folder
@@ -483,7 +483,8 @@ class TestClient(object):
         if cpu_count:
             self.client_cache.conan_config
             replace_in_file(os.path.join(self.client_cache.conan_conf_path),
-                            "# cpu_count = 1", "cpu_count = %s" % cpu_count)
+                            "# cpu_count = 1", "cpu_count = %s" % cpu_count,
+                            output=TestBufferConanOutput())
             # Invalidate the cached config
             self.client_cache.invalidate()
         if self.revisions: