Repository: conan-io/conan

Issue:
Provide Checksum header on all uploads
To help us debug your issue please explain:

- [x] I've read the [CONTRIBUTING guide](https://raw.githubusercontent.com/conan-io/conan/develop/.github/CONTRIBUTING.md).
- [x] I've specified the Conan version, operating system version and any tool that can be relevant.
- [x] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.

We should provide an X-Checksum header (preferably SHA2) on ALL uploads so that artifactory can check for corruption of a binary on upload.  Currently we only do it during checksum deploy, so we're not taking advantage of the corruption protection in artifactory.

Discussion:

Patch:
diff --git a/conans/client/build/cmake.py b/conans/client/build/cmake.py
index bc73e7184..40816f6b9 100644
--- a/conans/client/build/cmake.py
+++ b/conans/client/build/cmake.py
@@ -14,13 +14,14 @@ from conans.model.version import Version
 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 mkdir, get_abs_path, walk, decode_text
+from conans.util.env_reader import get_env
 
 
 class CMake(object):
 
     def __init__(self, conanfile, generator=None, cmake_system_name=True,
                  parallel=True, build_type=None, toolset=None, make_program=None,
-                 set_cmake_flags=False):
+                 set_cmake_flags=False, msbuild_verbosity=None):
         """
         :param conanfile: Conanfile instance
         :param generator: Generator name to use or none to autodetect
@@ -32,6 +33,7 @@ class CMake(object):
                 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.
                it's vital to set for certain projects (e.g. using CMAKE_SIZEOF_VOID_P or CMAKE_LIBRARY_ARCHITECTURE)
+        "param msbuild_verbosity: verbosity level for MSBuild (in case of Visual Studio generator)
         """
         if not isinstance(conanfile, ConanFile):
             raise ConanException("First argument of CMake() has to be ConanFile. Use CMake(self)")
@@ -55,6 +57,7 @@ class CMake(object):
         self.definitions = builder.get_definitions()
         self.toolset = toolset or get_toolset(self._settings)
         self.build_dir = None
+        self.msbuild_verbosity = msbuild_verbosity or get_env("CONAN_MSBUILD_VERBOSITY", "minimal")
 
     @property
     def build_folder(self):
@@ -202,8 +205,8 @@ class CMake(object):
         if target is not None:
             args = ["--target", target] + args
 
+        compiler_version = self._settings.get_safe("compiler.version")
         if self.generator and self.parallel:
-            compiler_version = self._settings.get_safe("compiler.version")
             if "Makefiles" in self.generator and "NMake" not in self.generator:
                 if "--" not in args:
                     args.append("--")
@@ -215,6 +218,13 @@ class CMake(object):
                 # Parallel for building projects in the solution
                 args.append("/m:%i" % cpu_count(output=self._conanfile.output))
 
+        if self.generator and self.msbuild_verbosity:
+            if "Visual Studio" in self.generator and \
+                    compiler_version and Version(compiler_version) >= "10":
+                if "--" not in args:
+                    args.append("--")
+                args.append("/verbosity:%s" % self.msbuild_verbosity)
+
         arg_list = join_arguments([
             args_to_string([build_dir]),
             self.build_config,
@@ -233,12 +243,17 @@ class CMake(object):
                                  "defined" % cmake_install_prefix_var_name)
         self._build(args=args, build_dir=build_dir, target="install")
 
-    def test(self, args=None, build_dir=None, target=None):
+    def test(self, args=None, build_dir=None, target=None, output_on_failure=False):
         if not self._conanfile.should_test:
             return
         if not target:
             target = "RUN_TESTS" if self.is_multi_configuration else "test"
-        self._build(args=args, build_dir=build_dir, target=target)
+
+        env = {'CTEST_OUTPUT_ON_FAILURE': '1' if output_on_failure else '0'}
+        if self.parallel:
+            env['CTEST_PARALLEL_LEVEL'] = str(cpu_count(self._conanfile.output))
+        with tools.environment_append(env):
+            self._build(args=args, build_dir=build_dir, target=target)
 
     @property
     def verbose(self):
diff --git a/conans/client/build/msbuild.py b/conans/client/build/msbuild.py
index b5eb39f03..a67c0146b 100644
--- a/conans/client/build/msbuild.py
+++ b/conans/client/build/msbuild.py
@@ -30,7 +30,7 @@ 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, properties=None, output_binary_log=None,
-              property_file_name=None):
+              property_file_name=None, verbosity=None):
 
         property_file_name = property_file_name or "conan_build.props"
         self.build_env.parallel = parallel
@@ -45,13 +45,14 @@ 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, properties=properties, output_binary_log=output_binary_log)
+                                       use_env=use_env, properties=properties, output_binary_log=output_binary_log,
+                                       verbosity=verbosity)
             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, properties=None, output_binary_log=None):
+                    use_env=False, properties=None, output_binary_log=None, verbosity=None):
 
         targets = targets or []
         properties = properties or {}
@@ -64,6 +65,7 @@ class MSBuild(object):
 
         build_type = build_type or self._settings.get_safe("build_type")
         arch = arch or self._settings.get_safe("arch")
+        verbosity = verbosity or get_env("CONAN_MSBUILD_VERBOSITY", "minimal")
         if not build_type:
             raise ConanException("Cannot build_sln_command, build_type not defined")
         if not arch:
@@ -116,6 +118,9 @@ class MSBuild(object):
         if toolset:
             command.append('/p:PlatformToolset="%s"' % toolset)
 
+        if verbosity:
+            command.append('/verbosity:%s' % verbosity)
+
         if props_file_path:
             command.append('/p:ForceImportBeforeCppTargets="%s"' % props_file_path)
 
diff --git a/conans/client/conf/__init__.py b/conans/client/conf/__init__.py
index 581a480ea..1c3dfae4c 100644
--- a/conans/client/conf/__init__.py
+++ b/conans/client/conf/__init__.py
@@ -98,7 +98,7 @@ request_timeout = 60                  # environment CONAN_REQUEST_TIMEOUT (secon
 # recipe_linter = False               # environment CONAN_RECIPE_LINTER
 # read_only_cache = True              # environment CONAN_READ_ONLY_CACHE
 # pylintrc = path/to/pylintrc_file    # environment CONAN_PYLINTRC
-# cache_no_locks = True               # Disable locking mechanism of local cache
+# cache_no_locks = True               # environment CONAN_CACHE_NO_LOCKS
 # user_home_short = your_path         # environment CONAN_USER_HOME_SHORT
 # use_always_short_paths = False      # environment CONAN_USE_ALWAYS_SHORT_PATHS
 # skip_vs_projects_upgrade = False    # environment CONAN_SKIP_VS_PROJECTS_UPGRADE
@@ -117,6 +117,8 @@ request_timeout = 60                  # environment CONAN_REQUEST_TIMEOUT (secon
 # cmake_find_root_path_mode_library   # environment CONAN_CMAKE_FIND_ROOT_PATH_MODE_LIBRARY
 # cmake_find_root_path_mode_include   # environment CONAN_CMAKE_FIND_ROOT_PATH_MODE_INCLUDE
 
+# msbuild_verbosity = minimal         # environment CONAN_MSBUILD_VERBOSITY
+
 # cpu_count = 1             # environment CONAN_CPU_COUNT
 
 # Change the default location for building test packages to a temporary folder
@@ -168,6 +170,7 @@ class ConanClientConfigParser(ConfigParser, object):
                "CONAN_COMPRESSION_LEVEL": self._env_c("general.compression_level", "CONAN_COMPRESSION_LEVEL", "9"),
                "CONAN_NON_INTERACTIVE": self._env_c("general.non_interactive", "CONAN_NON_INTERACTIVE", "False"),
                "CONAN_PYLINTRC": self._env_c("general.pylintrc", "CONAN_PYLINTRC", None),
+               "CONAN_CACHE_NO_LOCKS": self._env_c("general.cache_no_locks", "CONAN_CACHE_NO_LOCKS", "False"),
                "CONAN_PYLINT_WERR": self._env_c("general.pylint_werr", "CONAN_PYLINT_WERR", None),
                "CONAN_SYSREQUIRES_SUDO": self._env_c("general.sysrequires_sudo", "CONAN_SYSREQUIRES_SUDO", "False"),
                "CONAN_SYSREQUIRES_MODE": self._env_c("general.sysrequires_mode", "CONAN_SYSREQUIRES_MODE", "enabled"),
@@ -205,6 +208,9 @@ class ConanClientConfigParser(ConfigParser, object):
                "CONAN_TEMP_TEST_FOLDER": self._env_c("general.temp_test_folder", "CONAN_TEMP_TEST_FOLDER", "False"),
                "CONAN_SKIP_VS_PROJECTS_UPGRADE": self._env_c("general.skip_vs_projects_upgrade", "CONAN_SKIP_VS_PROJECTS_UPGRADE", "False"),
                "CONAN_HOOKS": self._env_c("hooks", "CONAN_HOOKS", None),
+               "CONAN_MSBUILD_VERBOSITY": self._env_c("general.msbuild_verbosity",
+                                                      "CONAN_MSBUILD_VERBOSITY",
+                                                      None)
                }
 
         # Filter None values
@@ -315,7 +321,7 @@ class ConanClientConfigParser(ConfigParser, object):
     @property
     def cache_no_locks(self):
         try:
-            return self.get_item("general.cache_no_locks")
+            return get_env("CONAN_CACHE_NO_LOCKS", False)
         except ConanException:
             return False
 
diff --git a/conans/client/rest/uploader_downloader.py b/conans/client/rest/uploader_downloader.py
index 39af8fa7f..c67c03cec 100644
--- a/conans/client/rest/uploader_downloader.py
+++ b/conans/client/rest/uploader_downloader.py
@@ -19,8 +19,11 @@ class Uploader(object):
         self.verify = verify
 
     def upload(self, url, abs_path, auth=None, dedup=False, retry=1, retry_wait=0, headers=None):
+        # Send always the header with the Sha1
+        headers = headers or {}
+        headers["X-Checksum-Sha1"] = sha1sum(abs_path)
         if dedup:
-            dedup_headers = {"X-Checksum-Deploy": "true", "X-Checksum-Sha1": sha1sum(abs_path)}
+            dedup_headers = {"X-Checksum-Deploy": "true"}
             if headers:
                 dedup_headers.update(headers)
             response = self.requester.put(url, data="", verify=self.verify, headers=dedup_headers,
@@ -28,7 +31,6 @@ class Uploader(object):
             if response.status_code == 201:  # Artifactory returns 201 if the file is there
                 return response
 
-        headers = headers or {}
         self.output.info("")
         # Actual transfer of the real content
         it = load_in_chunks(abs_path, self.chunk_size)
diff --git a/conans/client/tools/win.py b/conans/client/tools/win.py
index d73f566c2..3ecb9cb2b 100644
--- a/conans/client/tools/win.py
+++ b/conans/client/tools/win.py
@@ -120,7 +120,7 @@ def msvc_build_command(settings, sln_path, targets=None, upgrade_project=True, b
 @deprecation.deprecated(deprecated_in="1.2", removed_in="2.0",
                         details="Use the MSBuild() build helper instead")
 def build_sln_command(settings, sln_path, targets=None, upgrade_project=True, build_type=None,
-                      arch=None, parallel=True, toolset=None, platforms=None, output=None):
+                      arch=None, parallel=True, toolset=None, platforms=None, output=None, verbosity=None):
     """
     Use example:
         build_command = build_sln_command(self.settings, "myfile.sln", targets=["SDL2_image"])
@@ -141,7 +141,8 @@ def build_sln_command(settings, sln_path, targets=None, upgrade_project=True, bu
     command = tmp.get_command(sln_path, tmp_path,
                               targets=targets, upgrade_project=upgrade_project,
                               build_type=build_type, arch=arch, parallel=parallel,
-                              toolset=toolset, platforms=platforms, use_env=False)
+                              toolset=toolset, platforms=platforms, use_env=False,
+                              verbosity=verbosity)
 
     return command
 


Test patch:
diff --git a/conans/test/unittests/client/build/cmake_test.py b/conans/test/unittests/client/build/cmake_test.py
index f2ff1b0c4..5bd377007 100644
--- a/conans/test/unittests/client/build/cmake_test.py
+++ b/conans/test/unittests/client/build/cmake_test.py
@@ -1160,3 +1160,47 @@ build_type: [ Release]
             vcvars_mock.__exit__ = mock.MagicMock(return_value=None)
             cmake.build()
             self.assertTrue(vcvars_mock.called, "vcvars weren't called")
+
+    def test_msbuild_verbosity(self):
+        settings = Settings.loads(default_settings_yml)
+        settings.os = "Windows"
+        settings.compiler = "Visual Studio"
+        settings.compiler.version = "10"
+        settings.compiler.runtime = "MDd"
+        settings.arch = "x86"
+        settings.build_type = None
+
+        conan_file = ConanFileMock()
+        conan_file.settings = settings
+
+        cmake = CMake(conan_file)
+        cmake.build()
+        self.assertIn("/verbosity:minimal", conan_file.command)
+
+        cmake = CMake(conan_file, msbuild_verbosity="quiet")
+        cmake.build()
+        self.assertIn("/verbosity:quiet", conan_file.command)
+
+        with tools.environment_append({"CONAN_MSBUILD_VERBOSITY": "detailed"}):
+            cmake = CMake(conan_file)
+            cmake.build()
+            self.assertIn("/verbosity:detailed", conan_file.command)
+
+    def test_ctest_variables(self):
+        conanfile = ConanFileMock()
+        settings = Settings.loads(default_settings_yml)
+        settings.os = "Windows"
+        settings.compiler = "Visual Studio"
+        settings.compiler.version = "14"
+        conanfile.settings = settings
+
+        cmake = CMake(conanfile, parallel=False, generator="NMake Makefiles")
+        cmake.test()
+        self.assertEquals(conanfile.captured_env["CTEST_OUTPUT_ON_FAILURE"], "0")
+        self.assertNotIn("CTEST_PARALLEL_LEVEL", conanfile.captured_env)
+
+        with tools.environment_append({"CONAN_CPU_COUNT": "666"}):
+            cmake = CMake(conanfile, parallel=True, generator="NMake Makefiles")
+            cmake.test(output_on_failure=True)
+            self.assertEquals(conanfile.captured_env["CTEST_OUTPUT_ON_FAILURE"], "1")
+            self.assertEquals(conanfile.captured_env["CTEST_PARALLEL_LEVEL"], "666")
diff --git a/conans/test/unittests/client/build/msbuild_test.py b/conans/test/unittests/client/build/msbuild_test.py
index 76f417d48..666e562bf 100644
--- a/conans/test/unittests/client/build/msbuild_test.py
+++ b/conans/test/unittests/client/build/msbuild_test.py
@@ -145,3 +145,31 @@ class MSBuildTest(unittest.TestCase):
                                                                       "MyProp2": "MyValue2"})
         self.assertIn('/p:MyProp1="MyValue1"', command)
         self.assertIn('/p:MyProp2="MyValue2"', command)
+
+    def verbosity_default_test(self):
+        settings = MockSettings({"build_type": "Debug",
+                                 "compiler": "Visual Studio",
+                                 "arch": "x86_64"})
+        conanfile = MockConanfile(settings)
+        msbuild = MSBuild(conanfile)
+        command = msbuild.get_command("projecshould_flags_testt_file.sln")
+        self.assertIn('/verbosity:minimal', command)
+
+    def verbosity_env_test(self):
+        settings = MockSettings({"build_type": "Debug",
+                                 "compiler": "Visual Studio",
+                                 "arch": "x86_64"})
+        with tools.environment_append({"CONAN_MSBUILD_VERBOSITY": "detailed"}):
+            conanfile = MockConanfile(settings)
+            msbuild = MSBuild(conanfile)
+            command = msbuild.get_command("projecshould_flags_testt_file.sln")
+            self.assertIn('/verbosity:detailed', command)
+
+    def verbosity_explicit_test(self):
+        settings = MockSettings({"build_type": "Debug",
+                                 "compiler": "Visual Studio",
+                                 "arch": "x86_64"})
+        conanfile = MockConanfile(settings)
+        msbuild = MSBuild(conanfile)
+        command = msbuild.get_command("projecshould_flags_testt_file.sln", verbosity="quiet")
+        self.assertIn('/verbosity:quiet', command)
diff --git a/conans/test/unittests/util/build_sln_command_test.py b/conans/test/unittests/util/build_sln_command_test.py
index d2ca2c940..cd5364740 100644
--- a/conans/test/unittests/util/build_sln_command_test.py
+++ b/conans/test/unittests/util/build_sln_command_test.py
@@ -140,6 +140,7 @@ class BuildSLNCommandTest(unittest.TestCase):
         self.assertTrue(command.startswith('msbuild "dummy.sln" /p:Configuration="Debug" '
                                            '/p:Platform="ARM" '
                                            '/p:PlatformToolset="v110" '
+                                           '/verbosity:minimal '
                                            '/p:ForceImportBeforeCppTargets='), command)
 
     def properties_file_test(self):
@@ -154,6 +155,7 @@ class BuildSLNCommandTest(unittest.TestCase):
                                     parallel=False, output=ConanOutput(new_out))
         self.assertTrue(command.startswith('msbuild "dummy.sln" /p:Configuration="Debug" '
                                            '/p:Platform="ARM" '
+                                           '/verbosity:minimal '
                                            '/p:ForceImportBeforeCppTargets='), command)
         path_tmp = command.split("/p:ForceImportBeforeCppTargets=")[1][1:-1]  # remove quotes
         self.assertTrue(os.path.exists(path_tmp))