Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added the ability to output a binary log from MSBuild. #3626

Merged
merged 12 commits into from
Nov 29, 2018
33 changes: 29 additions & 4 deletions conans/client/build/msbuild.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import copy
import re
import subprocess

from conans import tools
from conans.client.build.visual_environment import (VisualStudioBuildEnvironment,
vs_build_type_flags, vs_std_cpp)
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


Expand All @@ -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

Expand All @@ -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 {}
Expand Down Expand Up @@ -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')

Expand Down Expand Up @@ -144,3 +155,17 @@ def _get_props_file_contents(self):
</Project>""".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))
116 changes: 116 additions & 0 deletions conans/test/build_helpers/msbuild_test.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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")

@unittest.skipUnless(platform.system() == "Windows", "Requires MSBuild")
@parameterized.expand([("True",), ("my_log.binlog",)])
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", "cppstd"

def build(self):
msbuild = MSBuild(self)
msbuild.build("MyProject.sln", output_binary_log="%s")
danimtb marked this conversation as resolved.
Show resolved Hide resolved
"""
client = TestClient()
files = get_vs_project_files()
files[CONANFILE] = conan_build_vs % value
client.save(files)
client.run("install .")
client.run("build .")

if value == "my_log.binlog":
log_name = value
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))