diff --git a/conans/client/build/msbuild.py b/conans/client/build/msbuild.py index 3ffb4005117..640269a4872 100644 --- a/conans/client/build/msbuild.py +++ b/conans/client/build/msbuild.py @@ -1,5 +1,6 @@ import copy import re +import subprocess from conans import tools from conans.client.build.visual_environment import (VisualStudioBuildEnvironment, @@ -7,8 +8,9 @@ from conans.client.tools.oss import cpu_count from conans.client.tools.win import vcvars_command from conans.errors import ConanException +from conans.model.version import Version from conans.util.env_reader import get_env -from conans.util.files import tmp_file +from conans.util.files import tmp_file, decode_text from conans.model.conan_file import ConanFile @@ -26,7 +28,7 @@ def __init__(self, conanfile): 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): + vcvars_ver=None, winsdk_version=None, properties=None, output_binary_log=None): self.build_env.parallel = parallel @@ -40,13 +42,13 @@ def build(self, project_file, targets=None, upgrade_project=True, build_type=Non targets=targets, upgrade_project=upgrade_project, build_type=build_type, arch=arch, parallel=parallel, toolset=toolset, platforms=platforms, - use_env=use_env, properties=properties) + use_env=use_env, properties=properties, output_binary_log=output_binary_log) 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): + use_env=False, properties=None, output_binary_log=None): targets = targets or [] properties = properties or {} @@ -87,6 +89,15 @@ def get_command(self, project_file, props_file_path=None, targets=None, upgrade_ self._output.warn("***** The configuration %s does not exist in this solution *****" % config) self._output.warn("Use 'platforms' argument to define your architectures") + if output_binary_log: + msbuild_version = MSBuild.get_version(self._settings) + if msbuild_version >= "15.3": # http://msbuildlog.com/ + command.append('/bl' if isinstance(output_binary_log, bool) + else '/bl:"%s"' % output_binary_log) + else: + raise ConanException("MSBuild version detected (%s) does not support " + "'output_binary_log' ('/bl')" % msbuild_version) + if use_env: command.append('/p:UseEnv=true') @@ -144,3 +155,17 @@ def _get_props_file_contents(self): """.format(**{"runtime_node": runtime_node, "additional_node": additional_node}) return template + + @staticmethod + def get_version(settings): + msbuild_cmd = "msbuild -version" + vcvars = vcvars_command(settings) + command = "%s && %s" % (vcvars, msbuild_cmd) + try: + out, err = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate() + version_line = decode_text(out).split("\n")[-1] + prog = re.compile("(\d+\.){2,3}\d+") + result = prog.match(version_line).group() + return Version(result) + except Exception as e: + raise ConanException("Error retrieving MSBuild version: '{}'".format(e)) diff --git a/conans/test/build_helpers/msbuild_test.py b/conans/test/build_helpers/msbuild_test.py index 239c572b9f3..301198726f4 100644 --- a/conans/test/build_helpers/msbuild_test.py +++ b/conans/test/build_helpers/msbuild_test.py @@ -1,10 +1,15 @@ +import os import platform import unittest +import mock from nose.plugins.attrib import attr +from parameterized import parameterized from conans import tools from conans.client.build.msbuild import MSBuild +from conans.errors import ConanException +from conans.model.version import Version from conans.paths import CONANFILE from conans.test.utils.conanfile import MockSettings, MockConanfile from conans.test.utils.tools import TestClient @@ -150,3 +155,114 @@ def build(self): client.save(files) client.run("create . danimtb/testing") self.assertIn("build() completed", client.out) + + @unittest.skipUnless(platform.system() == "Windows", "Requires MSBuild") + def binary_logging_on_test(self): + settings = MockSettings({"build_type": "Debug", + "compiler": "Visual Studio", + "compiler.version": "15", + "arch": "x86_64", + "compiler.runtime": "MDd"}) + conanfile = MockConanfile(settings) + msbuild = MSBuild(conanfile) + command = msbuild.get_command("dummy.sln", output_binary_log=True) + self.assertIn("/bl", command) + + @unittest.skipUnless(platform.system() == "Windows", "Requires MSBuild") + def binary_logging_on_with_filename_test(self): + bl_filename = "a_special_log.log" + settings = MockSettings({"build_type": "Debug", + "compiler": "Visual Studio", + "compiler.version": "15", + "arch": "x86_64", + "compiler.runtime": "MDd"}) + conanfile = MockConanfile(settings) + msbuild = MSBuild(conanfile) + command = msbuild.get_command("dummy.sln", output_binary_log=bl_filename) + expected_command = '/bl:"%s"' % bl_filename + self.assertIn(expected_command, command) + + def binary_logging_off_explicit_test(self): + settings = MockSettings({"build_type": "Debug", + "compiler": "Visual Studio", + "compiler.version": "15", + "arch": "x86_64", + "compiler.runtime": "MDd"}) + conanfile = MockConanfile(settings) + msbuild = MSBuild(conanfile) + command = msbuild.get_command("dummy.sln", output_binary_log=False) + self.assertNotIn("/bl", command) + + def binary_logging_off_implicit_test(self): + settings = MockSettings({"build_type": "Debug", + "compiler": "Visual Studio", + "compiler.version": "15", + "arch": "x86_64", + "compiler.runtime": "MDd"}) + conanfile = MockConanfile(settings) + msbuild = MSBuild(conanfile) + command = msbuild.get_command("dummy.sln") + self.assertNotIn("/bl", command) + + @unittest.skipUnless(platform.system() == "Windows", "Requires MSBuild") + @mock.patch("conans.client.build.msbuild.MSBuild.get_version") + def binary_logging_not_supported_test(self, mock_get_version): + mock_get_version.return_value = Version("14") + + mocked_settings = MockSettings({"build_type": "Debug", + "compiler": "Visual Studio", + "compiler.version": "15", + "arch": "x86_64", + "compiler.runtime": "MDd"}) + conanfile = MockConanfile(mocked_settings) + except_text = "MSBuild version detected (14) does not support 'output_binary_log' ('/bl')" + msbuild = MSBuild(conanfile) + + with self.assertRaises(ConanException) as exc: + msbuild.get_command("dummy.sln", output_binary_log=True) + self.assertIn(except_text, str(exc.exception)) + + @unittest.skipUnless(platform.system() == "Windows", "Requires MSBuild") + def get_version_test(self): + settings = MockSettings({"build_type": "Debug", + "compiler": "Visual Studio", + "compiler.version": "15", + "arch": "x86_64", + "compiler.runtime": "MDd"}) + version = MSBuild.get_version(settings) + self.assertRegexpMatches(version, "(\d+\.){2,3}\d+") + self.assertGreater(version, "15.1") + + @parameterized.expand([("True",), ("'my_log.binlog'",)]) + @unittest.skipUnless(platform.system() == "Windows", "Requires MSBuild") + def binary_log_build_test(self, value): + conan_build_vs = """ +from conans import ConanFile, MSBuild + +class HelloConan(ConanFile): + name = "Hello" + version = "1.2.1" + exports = "*" + settings = "os", "build_type", "arch", "compiler" + + def build(self): + msbuild = MSBuild(self) + msbuild.build("MyProject.sln", output_binary_log=%s) +""" + client = TestClient() + files = get_vs_project_files() + files[CONANFILE] = conan_build_vs % value + client.save(files) + client.run("install . -s compiler=\"Visual Studio\" -s compiler.version=15") + client.run("build .") + + if value == "'my_log.binlog'": + log_name = value[1:1] + flag = "/bl:%s" % log_name + else: + log_name = "msbuild.binlog" + flag = "/bl" + + self.assertIn(flag, client.out) + log_path = os.path.join(client.current_folder, log_name) + self.assertTrue(os.path.exists(log_path))