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

Feature: MSBuildDeps generator: CAExcludePath #8682

Merged
merged 7 commits into from Mar 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
57 changes: 41 additions & 16 deletions conan/tools/microsoft/msbuilddeps.py
@@ -1,7 +1,9 @@
import fnmatch
import os
import textwrap
from xml.dom import minidom

from jinja2 import Template

from conans.errors import ConanException
from conans.model.build_info import DepCppInfo
Expand Down Expand Up @@ -38,31 +40,34 @@ class MSBuildDeps(object):
<ImportGroup Label="Configurations">
</ImportGroup>
<PropertyGroup>
<conan_{name}_props_imported>True</conan_{name}_props_imported>
<conan_{{name}}_props_imported>True</conan_{{name}}_props_imported>
</PropertyGroup>
<PropertyGroup>
<LocalDebuggerEnvironment>PATH=%PATH%;$(Conan{name}BinaryDirectories)$(LocalDebuggerEnvironment)</LocalDebuggerEnvironment>
<LocalDebuggerEnvironment>PATH=%PATH%;$(Conan{{name}}BinaryDirectories)$(LocalDebuggerEnvironment)</LocalDebuggerEnvironment>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
{% if ca_exclude -%}
<CAExcludePath>$(Conan{{name}}IncludeDirectories);$(CAExcludePath)</CAExcludePath>
{%- endif %}
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(Conan{name}IncludeDirectories)%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>$(Conan{name}PreprocessorDefinitions)%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalOptions>$(Conan{name}CompilerFlags) %(AdditionalOptions)</AdditionalOptions>
<AdditionalIncludeDirectories>$(Conan{{name}}IncludeDirectories)%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>$(Conan{{name}}PreprocessorDefinitions)%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalOptions>$(Conan{{name}}CompilerFlags) %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(Conan{name}LibraryDirectories)%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>$(Conan{name}Libraries)%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>$(Conan{name}SystemDeps)%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalOptions>$(Conan{name}LinkerFlags) %(AdditionalOptions)</AdditionalOptions>
<AdditionalLibraryDirectories>$(Conan{{name}}LibraryDirectories)%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>$(Conan{{name}}Libraries)%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>$(Conan{{name}}SystemDeps)%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalOptions>$(Conan{{name}}LinkerFlags) %(AdditionalOptions)</AdditionalOptions>
</Link>
<Midl>
<AdditionalIncludeDirectories>$(Conan{name}IncludeDirectories)%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(Conan{{name}}IncludeDirectories)%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</Midl>
<ResourceCompile>
<AdditionalIncludeDirectories>$(Conan{name}IncludeDirectories)%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>$(Conan{name}PreprocessorDefinitions)%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalOptions>$(Conan{name}CompilerFlags) %(AdditionalOptions)</AdditionalOptions>
<AdditionalIncludeDirectories>$(Conan{{name}}IncludeDirectories)%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>$(Conan{{name}}PreprocessorDefinitions)%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalOptions>$(Conan{{name}}CompilerFlags) %(AdditionalOptions)</AdditionalOptions>
</ResourceCompile>
</ItemDefinitionGroup>
</Project>
Expand All @@ -75,6 +80,15 @@ def __init__(self, conanfile):
'x86_64': 'x64'}.get(str(conanfile.settings.arch))
# TODO: this is ugly, improve this
self.output_path = os.getcwd()
# ca_exclude section
self.exclude_code_analysis = None
ca_exclude = self._conanfile.conf["tools.microsoft.msbuilddeps"].exclude_code_analysis
if ca_exclude is not None:
# TODO: Accept single strings, not lists
self.exclude_code_analysis = eval(ca_exclude)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we really okay if eval usage? it seems to be pretty dangerous, as it may allow arbitrary code execution:
https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, as well as every Conan recipe, how this would be more dangerous than Conan python evaluation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can avoid eval then? if use just a regexp, if regexp matches nothing, it acts effectively as disabled matching

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem is that all the matches we have in Conan for matching packages references are fnmatch based. Introducing now regex for this would be very asymmetric. And for fnmatch, we should probably allow multiple patterns, as it is not as expressive.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets merge it, but keep it not documented while we finalize the UX, but there are some template changes that are useful for other PRs.

if not isinstance(self.exclude_code_analysis, list):
raise ConanException("tools.microsoft.msbuilddeps:exclude_code_analysis must be a"
" list of package names patterns like ['pkga*']")

def generate(self):
# TODO: Apply config from command line, something like
Expand Down Expand Up @@ -165,7 +179,7 @@ def add_valid_ext(libname):
'definitions': "".join("%s;" % d for d in cpp_info.defines),
'compiler_flags': " ".join(cpp_info.cxxflags + cpp_info.cflags),
'linker_flags': " ".join(cpp_info.sharedlinkflags),
'exe_flags': " ".join(cpp_info.exelinkflags)
'exe_flags': " ".join(cpp_info.exelinkflags),
}
formatted_template = self._vars_conf_props.format(**fields)
return formatted_template
Expand All @@ -178,7 +192,18 @@ def _pkg_props(self, name_multi, dep_name, vars_props_name, condition, cpp_info)
else:
content_multi = self._dep_props

content_multi = content_multi.format(name=dep_name)
# TODO: This must include somehow the user/channel, most likely pattern to exclude/include
# Probably also the negation pattern, exclude all not @mycompany/*
ca_exclude = False
if isinstance(self.exclude_code_analysis, list):
for pattern in self.exclude_code_analysis:
if fnmatch.fnmatch(dep_name, pattern):
ca_exclude = True
break
else:
ca_exclude = self.exclude_code_analysis

content_multi = Template(content_multi).render(name=dep_name, ca_exclude=ca_exclude)

# parse the multi_file and add new import statement if needed
dom = minidom.parseString(content_multi)
Expand Down Expand Up @@ -228,7 +253,7 @@ def _content(self):
direct_deps = self._conanfile.dependencies.direct_host_requires
result[general_name] = self._deps_props(general_name, direct_deps)
for dep in self._conanfile.dependencies.host_requires:
cpp_info = DepCppInfo(dep.cpp_info) # To account for automatic component aggregation
cpp_info = DepCppInfo(dep.cpp_info) # To account for automatic component aggregation
# One file per configuration, with just the variables
vars_props_name = "conan_%s%s.props" % (dep.name, conf_name)
vars_conf_content = self._pkg_config_props(dep.name, cpp_info)
Expand Down
52 changes: 52 additions & 0 deletions conans/test/functional/toolchains/microsoft/test_msbuilddeps.py
Expand Up @@ -5,6 +5,8 @@

import pytest

from parameterized import parameterized

from conans.test.assets.cpp_test_files import cpp_hello_conan_files
from conans.test.assets.genconanfile import GenConanfile
from conans.test.assets.sources import gen_function_cpp
Expand Down Expand Up @@ -607,3 +609,53 @@ def build(self):
self.assertIn("conan_tool.props", deps)
client.run("create . pkg/0.1@")
self.assertIn("Conan_tools.props in deps", client.out)

@parameterized.expand([("['*']", True, True),
("['pkga']", True, False),
("['pkgb']", False, True),
("['pkg*']", True, True),
("['pkga', 'pkgb']", True, True),
("['*a', '*b']", True, True),
("['nonexist']", False, False),
])
def test_exclude_code_analysis(self, pattern, exclude_a, exclude_b):
client = TestClient()
client.save({"conanfile.py": GenConanfile()})
client.run("create . pkga/1.0@")
client.run("create . pkgb/1.0@")

conanfile = textwrap.dedent("""
from conans import ConanFile, MSBuild
class HelloConan(ConanFile):
settings = "os", "build_type", "compiler", "arch"
requires = "pkgb/1.0@", "pkga/1.0"
generators = "msbuild"
def build(self):
msbuild = MSBuild(self)
msbuild.build("MyProject.sln")
""")
profile = textwrap.dedent("""
include(default)
[conf]
tools.microsoft.msbuilddeps:exclude_code_analysis = %s
""" % pattern)

client.save({"conanfile.py": conanfile,
"profile": profile})
client.run("install . --profile profile")
depa = client.load("conan_pkga.props")
depb = client.load("conan_pkgb.props")

if exclude_a:
inc = "$(ConanpkgaIncludeDirectories)"
ca_exclude = "<CAExcludePath>%s;$(CAExcludePath)</CAExcludePath>" % inc
assert ca_exclude in depa
else:
assert "CAExcludePath" not in depa

if exclude_b:
inc = "$(ConanpkgbIncludeDirectories)"
ca_exclude = "<CAExcludePath>%s;$(CAExcludePath)</CAExcludePath>" % inc
assert ca_exclude in depb
else:
assert "CAExcludePath" not in depb