From 56a1aef3f1fccf0d1663c89e9b557a4c17c20d64 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 30 Mar 2021 02:25:57 +0200 Subject: [PATCH] Feature/tools msbuilddeps visitor improved (#8733) * - add CAExcludePath Signed-off-by: SSE4 * Update conan/tools/microsoft/msbuilddeps.py Co-authored-by: James * Update conan/tools/microsoft/msbuilddeps.py Co-authored-by: James * Update conan/tools/microsoft/msbuilddeps.py Co-authored-by: James * fixing code * visitor improved Co-authored-by: SSE4 --- conan/tools/microsoft/msbuilddeps.py | 199 +++++++++--------- .../toolchains/microsoft/test_msbuilddeps.py | 13 +- 2 files changed, 114 insertions(+), 98 deletions(-) diff --git a/conan/tools/microsoft/msbuilddeps.py b/conan/tools/microsoft/msbuilddeps.py index d6ca1adb11a..03c070eca22 100644 --- a/conan/tools/microsoft/msbuilddeps.py +++ b/conan/tools/microsoft/msbuilddeps.py @@ -14,40 +14,42 @@ class MSBuildDeps(object): - _vars_conf_props = textwrap.dedent("""\ + _vars_props = textwrap.dedent("""\ - {root_folder} - {compiler_flags} - {linker_flags} - {definitions} - {include_dirs} - {res_dirs} - {lib_dirs} - {bin_dirs} - {libs} - {system_libs} + {{root_folder}} + {{compiler_flags}} + {{linker_flags}} + {{definitions}} + {{include_dirs}} + {{res_dirs}} + {{lib_dirs}} + {{bin_dirs}} + {{libs}} + {{system_libs}} + {{dependencies}} """) - _dep_props = textwrap.dedent("""\ + _conf_props = textwrap.dedent("""\ + {% for dep in deps %} + + {% endfor %} - + + - - True - PATH=%PATH%;$(Conan{{name}}BinaryDirectories)$(LocalDebuggerEnvironment) WindowsLocalDebugger - {% if ca_exclude -%} + {% if ca_exclude %} $(Conan{{name}}IncludeDirectories);$(CAExcludePath) - {%- endif %} + {% endif %} @@ -58,7 +60,7 @@ class MSBuildDeps(object): $(Conan{{name}}LibraryDirectories)%(AdditionalLibraryDirectories) $(Conan{{name}}Libraries)%(AdditionalDependencies) - $(Conan{{name}}SystemDeps)%(AdditionalDependencies) + $(Conan{{name}}SystemLibs)%(AdditionalDependencies) $(Conan{{name}}LinkerFlags) %(AdditionalOptions) @@ -73,6 +75,25 @@ class MSBuildDeps(object): """) + _dep_props = textwrap.dedent("""\ + + + + + + True + + + """) + + _all_props = textwrap.dedent("""\ + + + + + + """) + def __init__(self, conanfile): self._conanfile = conanfile self.configuration = conanfile.settings.build_type @@ -121,47 +142,10 @@ def _condition(self): condition = " And ".join("'$(%s)' == '%s'" % (k, v) for k, v in props) return condition - def _deps_props(self, name_general, deps): - """ this is a .props file including all declared dependencies + def _vars_props_file(self, name, cpp_info, deps): + """ + content for conan_vars_poco_x86_release.props, containing the variables """ - # read the existing multi_filename or use the template if it doesn't exist - template = textwrap.dedent("""\ - - - - - - """) - multi_path = os.path.join(self.output_path, name_general) - if os.path.isfile(multi_path): - content_multi = load(multi_path) - else: - content_multi = template - - # parse the multi_file and add a new import statement if needed - dom = minidom.parseString(content_multi) - import_group = dom.getElementsByTagName('ImportGroup')[0] - children = import_group.getElementsByTagName("Import") - for dep in deps: - conf_props_name = "conan_%s.props" % dep.name - for node in children: - if conf_props_name == node.getAttribute("Project"): - # the import statement already exists - break - else: - # create a new import statement - import_node = dom.createElement('Import') - dep_imported = "'$(conan_%s_props_imported)' != 'True'" % dep.name - import_node.setAttribute('Project', conf_props_name) - import_node.setAttribute('Condition', dep_imported) - # add it to the import group - import_group.appendChild(import_node) - content_multi = dom.toprettyxml() - # To remove all extra blank lines - content_multi = "\n".join(line for line in content_multi.splitlines() if line.strip()) - return content_multi - - def _pkg_config_props(self, name, cpp_info): # returns a .props file with the variables definition for one package for one configuration def add_valid_ext(libname): ext = os.path.splitext(libname)[1] @@ -180,18 +164,15 @@ def add_valid_ext(libname): 'compiler_flags': " ".join(cpp_info.cxxflags + cpp_info.cflags), 'linker_flags': " ".join(cpp_info.sharedlinkflags), 'exe_flags': " ".join(cpp_info.exelinkflags), + 'dependencies': ";".join(deps) } - formatted_template = self._vars_conf_props.format(**fields) + formatted_template = Template(self._vars_props).render(**fields) return formatted_template - def _pkg_props(self, name_multi, dep_name, vars_props_name, condition, cpp_info): - # read the existing mult_filename or use the template if it doesn't exist - multi_path = os.path.join(self.output_path, name_multi) - if os.path.isfile(multi_path): - content_multi = load(multi_path) - else: - content_multi = self._dep_props - + def _conf_props_file(self, dep_name, vars_props_name, deps): + """ + content for conan_poco_x86_release.props, containing the activation + """ # 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 @@ -203,43 +184,71 @@ def _pkg_props(self, name_multi, dep_name, vars_props_name, condition, cpp_info) else: ca_exclude = self.exclude_code_analysis - content_multi = Template(content_multi).render(name=dep_name, ca_exclude=ca_exclude) + template = Template(self._conf_props, trim_blocks=True, lstrip_blocks=True) + content_multi = template.render(name=dep_name, ca_exclude=ca_exclude, + vars_filename=vars_props_name, deps=deps) + return content_multi + + def _dep_props_file(self, name, name_general, dep_props_filename, condition): + multi_path = os.path.join(self.output_path, name_general) + if os.path.isfile(multi_path): + content_multi = load(multi_path) + else: + content_multi = self._dep_props + content_multi = Template(content_multi).render({"name": name}) # parse the multi_file and add new import statement if needed dom = minidom.parseString(content_multi) - import_deps, import_vars = dom.getElementsByTagName('ImportGroup') - - # Transitive Deps - children = import_deps.getElementsByTagName("Import") - for dep in cpp_info.public_deps: - dep_props_name = "conan_%s.props" % dep - dep_imported = "'$(conan_%s_props_imported)' != 'True'" % dep - for node in children: - if (dep_props_name == node.getAttribute("Project") and - dep_imported == node.getAttribute("Condition")): - break # the import statement already exists - else: # create a new import statement - import_node = dom.createElement('Import') - import_node.setAttribute('Condition', dep_imported) - import_node.setAttribute('Project', dep_props_name) - import_deps.appendChild(import_node) + import_vars = dom.getElementsByTagName('ImportGroup')[0] # Current vars children = import_vars.getElementsByTagName("Import") for node in children: - if (vars_props_name == node.getAttribute("Project") and + if (dep_props_filename == node.getAttribute("Project") and condition == node.getAttribute("Condition")): break # the import statement already exists else: # create a new import statement import_node = dom.createElement('Import') import_node.setAttribute('Condition', condition) - import_node.setAttribute('Project', vars_props_name) + import_node.setAttribute('Project', dep_props_filename) import_vars.appendChild(import_node) content_multi = dom.toprettyxml() content_multi = "\n".join(line for line in content_multi.splitlines() if line.strip()) return content_multi + def _all_props_file(self, name_general, deps): + """ this is a .props file including all declared dependencies + """ + multi_path = os.path.join(self.output_path, name_general) + if os.path.isfile(multi_path): + content_multi = load(multi_path) + else: + content_multi = self._all_props + + # parse the multi_file and add a new import statement if needed + dom = minidom.parseString(content_multi) + import_group = dom.getElementsByTagName('ImportGroup')[0] + children = import_group.getElementsByTagName("Import") + for dep in deps: + conf_props_name = "conan_%s.props" % dep.name + for node in children: + if conf_props_name == node.getAttribute("Project"): + # the import statement already exists + break + else: + # create a new import statement + import_node = dom.createElement('Import') + dep_imported = "'$(conan_%s_props_imported)' != 'True'" % dep.name + import_node.setAttribute('Project', conf_props_name) + import_node.setAttribute('Condition', dep_imported) + # add it to the import group + import_group.appendChild(import_node) + content_multi = dom.toprettyxml() + # To remove all extra blank lines + content_multi = "\n".join(line for line in content_multi.splitlines() if line.strip()) + return content_multi + def _content(self): # We cannot use self._conanfile.warn(), because that fails for virtual conanfile print("*** The 'msbuild' generator is EXPERIMENTAL ***") @@ -251,17 +260,19 @@ def _content(self): condition = self._condition() # Include all direct build_requires for host context. This might change direct_deps = self._conanfile.dependencies.direct_host_requires - result[general_name] = self._deps_props(general_name, direct_deps) + result[general_name] = self._all_props_file(general_name, direct_deps) for dep in self._conanfile.dependencies.host_requires: cpp_info = DepCppInfo(dep.cpp_info) # To account for automatic component aggregation + public_deps = [d.name for d in dep.dependencies.direct_host_requires] # 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) - result[vars_props_name] = vars_conf_content + vars_props_name = "conan_%s_vars%s.props" % (dep.name, conf_name) + result[vars_props_name] = self._vars_props_file(dep.name, cpp_info, public_deps) + props_name = "conan_%s%s.props" % (dep.name, conf_name) + result[props_name] = self._conf_props_file(dep.name, vars_props_name, public_deps) # The entry point for each package, it will have conditionals to the others - props_name = "conan_%s.props" % dep.name - dep_content = self._pkg_props(props_name, dep.name, vars_props_name, condition, cpp_info) - result[props_name] = dep_content + dep_name = "conan_%s.props" % dep.name + dep_content = self._dep_props_file(dep.name, dep_name, props_name, condition) + result[dep_name] = dep_content return result diff --git a/conans/test/functional/toolchains/microsoft/test_msbuilddeps.py b/conans/test/functional/toolchains/microsoft/test_msbuilddeps.py index 373bfb14a69..cde67216fc4 100644 --- a/conans/test/functional/toolchains/microsoft/test_msbuilddeps.py +++ b/conans/test/functional/toolchains/microsoft/test_msbuilddeps.py @@ -455,7 +455,7 @@ def test_install_reference(self): client.run("install mypkg/0.1@ -g MSBuildDeps") self.assertIn("Generator 'MSBuildDeps' calling 'generate()'", client.out) # https://github.com/conan-io/conan/issues/8163 - props = client.load("conan_mypkg_release_x64.props") # default Release/x64 + props = client.load("conan_mypkg_vars_release_x64.props") # default Release/x64 folder = props[props.find("")+len("") :props.find("")] self.assertTrue(os.path.isfile(os.path.join(folder, "conaninfo.txt"))) @@ -610,6 +610,7 @@ def build(self): 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), @@ -625,7 +626,9 @@ def test_exclude_code_analysis(self, pattern, exclude_a, exclude_b): client.run("create . pkgb/1.0@") conanfile = textwrap.dedent(""" - from conans import ConanFile, MSBuild + from conans import ConanFile + from conan.tools.microsoft import MSBuild + class HelloConan(ConanFile): settings = "os", "build_type", "compiler", "arch" requires = "pkgb/1.0@", "pkga/1.0" @@ -636,6 +639,8 @@ def build(self): """) profile = textwrap.dedent(""" include(default) + build_type=Release + arch=x86_64 [conf] tools.microsoft.msbuilddeps:exclude_code_analysis = %s """ % pattern) @@ -643,8 +648,8 @@ def build(self): client.save({"conanfile.py": conanfile, "profile": profile}) client.run("install . --profile profile") - depa = client.load("conan_pkga.props") - depb = client.load("conan_pkgb.props") + depa = client.load("conan_pkga_release_x64.props") + depb = client.load("conan_pkgb_release_x64.props") if exclude_a: inc = "$(ConanpkgaIncludeDirectories)"