forked from conan-io/conan
/
msbuilddeps.py
257 lines (233 loc) · 12.8 KB
/
msbuilddeps.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
import fnmatch
import os
import textwrap
from xml.dom import minidom
from conans.errors import ConanException
from conans.util.files import load, save
VALID_LIB_EXTENSIONS = (".so", ".lib", ".a", ".dylib", ".bc")
class MSBuildDeps(object):
_vars_conf_props = textwrap.dedent("""\
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="ConanVariables">
<Conan{name}RootFolder>{root_folder}</Conan{name}RootFolder>
<Conan{name}CompilerFlags>{compiler_flags}</Conan{name}CompilerFlags>
<Conan{name}LinkerFlags>{linker_flags}</Conan{name}LinkerFlags>
<Conan{name}PreprocessorDefinitions>{definitions}</Conan{name}PreprocessorDefinitions>
<Conan{name}IncludeDirectories>{include_dirs}</Conan{name}IncludeDirectories>
<Conan{name}ResourceDirectories>{res_dirs}</Conan{name}ResourceDirectories>
<Conan{name}LibraryDirectories>{lib_dirs}</Conan{name}LibraryDirectories>
<Conan{name}BinaryDirectories>{bin_dirs}</Conan{name}BinaryDirectories>
<Conan{name}Libraries>{libs}</Conan{name}Libraries>
<Conan{name}SystemDeps>{system_libs}</Conan{name}SystemDeps>
<Conan{name}CAExcludeDirectories>{ca_exclude_dirs}</Conan{name}CAExcludeDirectories>
</PropertyGroup>
</Project>
""")
_dep_props = textwrap.dedent("""\
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="ConanDependencies">
</ImportGroup>
<ImportGroup Label="Configurations">
</ImportGroup>
<PropertyGroup>
<conan_{name}_props_imported>True</conan_{name}_props_imported>
</PropertyGroup>
<PropertyGroup>
<LocalDebuggerEnvironment>PATH=%PATH%;$(Conan{name}BinaryDirectories)$(LocalDebuggerEnvironment)</LocalDebuggerEnvironment>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
<CAExcludePath>$(Conan{name}CAExcludeDirectories);$(CAExcludePath)</CAExcludePath>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<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>
</Link>
<Midl>
<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>
</ResourceCompile>
</ItemDefinitionGroup>
</Project>
""")
def __init__(self, conanfile):
self._conanfile = conanfile
self.configuration = conanfile.settings.build_type
self.platform = {'x86': 'Win32',
'x86_64': 'x64'}.get(str(conanfile.settings.arch))
# TODO: this is ugly, improve this
self.output_path = os.getcwd()
self.exclude_code_analysis = None
def generate(self):
# TODO: Apply config from command line, something like
# configuration = self.conanfile.config.generators["msbuild"].configuration
# if configuration is not None:
# self.configuration = configuration
# platform
# config_filename
# TODO: This is duplicated in write_generators() function, would need to be moved
# to generators and called from there
exclude_code_analysis = self._conanfile.conf["tools.microsoft.msbuilddeps"].exclude_code_analysis
if exclude_code_analysis is not None:
self.exclude_code_analysis = eval(exclude_code_analysis)
assert isinstance(self.exclude_code_analysis, (bool, list))
if self.configuration is None:
raise ConanException("MSBuildDeps.configuration is None, it should have a value")
if self.platform is None:
raise ConanException("MSBuildDeps.platform is None, it should have a value")
generator_files = self._content()
for generator_file, content in generator_files.items():
generator_file_path = os.path.join(self.output_path, generator_file)
save(generator_file_path, content)
def _config_filename(self):
# Default name
props = [("Configuration", self.configuration),
("Platform", self.platform)]
name = "".join("_%s" % v for _, v in props)
return name.lower()
def _condition(self):
props = [("Configuration", self.configuration),
("Platform", self.platform)]
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
"""
# read the existing multi_filename or use the template if it doesn't exist
template = textwrap.dedent("""\
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="ConanDependencies" >
</ImportGroup>
</Project>
""")
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
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
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]
return '%s;' % libname if ext in VALID_LIB_EXTENSIONS else '%s.lib;' % libname
exclude_code_analysis = False
if isinstance(self.exclude_code_analysis, list):
for pattern in self.exclude_code_analysis :
if fnmatch.fnmatch(name, pattern):
exclude_code_analysis = True
break
else:
exclude_code_analysis = self.exclude_code_analysis
fields = {
'name': name,
'root_folder': cpp_info.rootpath,
'bin_dirs': "".join("%s;" % p for p in cpp_info.bin_paths),
'res_dirs': "".join("%s;" % p for p in cpp_info.res_paths),
'include_dirs': "".join("%s;" % p for p in cpp_info.include_paths),
'lib_dirs': "".join("%s;" % p for p in cpp_info.lib_paths),
'libs': "".join([add_valid_ext(lib) for lib in cpp_info.libs]),
'system_libs': "".join([add_valid_ext(sys_dep) for sys_dep in cpp_info.system_libs]),
'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),
'ca_exclude_dirs': '$(Conan{}IncludeDirectories)'.format(name) if exclude_code_analysis else ''
}
formatted_template = self._vars_conf_props.format(**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
content_multi = content_multi.format(name=dep_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)
# Current vars
children = import_vars.getElementsByTagName("Import")
for node in children:
if (vars_props_name == 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_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 _content(self):
# We cannot use self._conanfile.warn(), because that fails for virtual conanfile
print("*** The 'msbuild' generator is EXPERIMENTAL ***")
if not self._conanfile.settings.get_safe("build_type"):
raise ConanException("The 'msbuild' generator requires a 'build_type' setting value")
result = {}
general_name = "conandeps.props"
conf_name = self._config_filename()
condition = self._condition()
# Include all direct build_requires for host context. This might change
direct_deps = self._conanfile.deps_cpp_info.direct_host_deps
result[general_name] = self._deps_props(general_name, direct_deps)
for dep_name, cpp_info in self._conanfile.deps_cpp_info.dependencies:
# 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
# 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
return result