forked from conan-io/conan
/
presets.py
256 lines (211 loc) · 10 KB
/
presets.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
import json
import os
import platform
from conan.tools.cmake.layout import get_build_folder_custom_vars
from conan.tools.cmake.utils import is_multi_configuration
from conan.tools.microsoft import is_msvc
from conans.errors import ConanException
from conans.util.files import save, load
def _build_preset(conanfile, multiconfig):
build_type = conanfile.settings.get_safe("build_type")
configure_preset_name = _configure_preset_name(conanfile, multiconfig)
ret = {"name": _build_preset_name(conanfile),
"configurePreset": configure_preset_name}
if multiconfig:
ret["configuration"] = build_type
return ret
def _build_preset_name(conanfile):
build_type = conanfile.settings.get_safe("build_type")
custom_conf = get_build_folder_custom_vars(conanfile)
if custom_conf:
if build_type:
return "{}-{}".format(custom_conf, build_type.lower())
else:
return custom_conf
return build_type.lower() if build_type else "default"
def _configure_preset_name(conanfile, multiconfig):
build_type = conanfile.settings.get_safe("build_type")
custom_conf = get_build_folder_custom_vars(conanfile)
if multiconfig or not build_type:
return "default" if not custom_conf else custom_conf
if custom_conf:
return "{}-{}".format(custom_conf, str(build_type).lower())
else:
return str(build_type).lower()
def _configure_preset(conanfile, generator, cache_variables, toolchain_file, multiconfig):
build_type = conanfile.settings.get_safe("build_type")
name = _configure_preset_name(conanfile, multiconfig)
if not multiconfig and build_type:
cache_variables["CMAKE_BUILD_TYPE"] = build_type
ret = {
"name": name,
"displayName": "'{}' config".format(name),
"description": "'{}' configure using '{}' generator".format(name, generator),
"generator": generator,
"cacheVariables": cache_variables,
}
if "Ninja" in generator and is_msvc(conanfile):
toolset_arch = conanfile.conf.get("tools.cmake.cmaketoolchain:toolset_arch")
if toolset_arch:
toolset_arch = "host={}".format(toolset_arch)
ret["toolset"] = {
"value": toolset_arch,
"strategy": "external"
}
arch = {"x86": "x86",
"x86_64": "x64",
"armv7": "ARM",
"armv8": "ARM64"}.get(conanfile.settings.get_safe("arch"))
if arch:
ret["architecture"] = {
"value": arch,
"strategy": "external"
}
if not _forced_schema_2(conanfile):
ret["toolchainFile"] = toolchain_file
else:
ret["cacheVariables"]["CMAKE_TOOLCHAIN_FILE"] = toolchain_file
if conanfile.build_folder:
# If we are installing a ref: "conan install <ref>", we don't have build_folder, because
# we don't even have a conanfile with a `layout()` to determine the build folder.
# If we install a local conanfile: "conan install ." with a layout(), it will be available.
ret["binaryDir"] = conanfile.build_folder
return ret
def _forced_schema_2(conanfile):
version = conanfile.conf.get("tools.cmake.cmaketoolchain.presets:max_schema_version",
check_type=int)
if not version:
return False
if version < 2:
raise ConanException("The minimum value for 'tools.cmake.cmaketoolchain.presets:"
"schema_version' is 2")
if version < 4:
return True
return False
def _schema_version(conanfile, default):
if _forced_schema_2(conanfile):
return 2
return default
def _contents(conanfile, toolchain_file, cache_variables, generator):
"""
Contents for the CMakePresets.json
It uses schema version 3 unless it is forced to 2
"""
ret = {"version": _schema_version(conanfile, default=3),
"cmakeMinimumRequired": {"major": 3, "minor": 15, "patch": 0},
"configurePresets": [],
"buildPresets": [],
"testPresets": []
}
multiconfig = is_multi_configuration(generator)
ret["buildPresets"].append(_build_preset(conanfile, multiconfig))
_conf = _configure_preset(conanfile, generator, cache_variables, toolchain_file, multiconfig)
ret["configurePresets"].append(_conf)
return ret
def write_cmake_presets(conanfile, toolchain_file, generator, cache_variables):
cache_variables = cache_variables or {}
if platform.system() == "Windows" and generator == "MinGW Makefiles":
if "CMAKE_SH" not in cache_variables:
cache_variables["CMAKE_SH"] = "CMAKE_SH-NOTFOUND"
cmake_make_program = conanfile.conf.get("tools.gnu:make_program", default=cache_variables.get("CMAKE_MAKE_PROGRAM"))
if cmake_make_program:
cmake_make_program = cmake_make_program.replace("\\", "/")
cache_variables["CMAKE_MAKE_PROGRAM"] = cmake_make_program
if "CMAKE_POLICY_DEFAULT_CMP0091" not in cache_variables:
cache_variables["CMAKE_POLICY_DEFAULT_CMP0091"] = "NEW"
preset_path = os.path.join(conanfile.generators_folder, "CMakePresets.json")
multiconfig = is_multi_configuration(generator)
if os.path.exists(preset_path):
data = json.loads(load(preset_path))
build_preset = _build_preset(conanfile, multiconfig)
position = _get_already_existing_preset_index(build_preset["name"], data["buildPresets"])
if position is not None:
data["buildPresets"][position] = build_preset
else:
data["buildPresets"].append(build_preset)
configure_preset = _configure_preset(conanfile, generator, cache_variables, toolchain_file,
multiconfig)
position = _get_already_existing_preset_index(configure_preset["name"],
data["configurePresets"])
if position is not None:
data["configurePresets"][position] = configure_preset
else:
data["configurePresets"].append(configure_preset)
else:
data = _contents(conanfile, toolchain_file, cache_variables, generator)
data = json.dumps(data, indent=4)
save(preset_path, data)
save_cmake_user_presets(conanfile, preset_path)
def save_cmake_user_presets(conanfile, preset_path):
# Try to save the CMakeUserPresets.json if layout declared and CMakeLists.txt found
if conanfile.source_folder and conanfile.source_folder != conanfile.generators_folder:
if os.path.exists(os.path.join(conanfile.source_folder, "CMakeLists.txt")):
"""
Contents for the CMakeUserPresets.json
It uses schema version 4 unless it is forced to 2
"""
user_presets_path = os.path.join(conanfile.source_folder, "CMakeUserPresets.json")
if not os.path.exists(user_presets_path):
data = {"version": _schema_version(conanfile, default=4),
"vendor": {"conan": dict()}}
else:
data = json.loads(load(user_presets_path))
if "conan" not in data.get("vendor", {}):
# The file is not ours, we cannot overwrite it
return
data = _append_user_preset_path(conanfile, data, preset_path)
data = json.dumps(data, indent=4)
save(user_presets_path, data)
def _get_already_existing_preset_index(name, presets):
"""Get the index of a Preset with a given name, this is used to replace it with updated contents
"""
positions = [index for index, p in enumerate(presets)
if p["name"] == name]
if positions:
return positions[0]
return None
def _append_user_preset_path(conanfile, data, preset_path):
""" - Appends a 'include' to preset_path if the schema supports it.
- Otherwise it merges to "data" all the configurePresets, buildPresets etc from the
read preset_path.
"""
if not _forced_schema_2(conanfile):
if "include" not in data:
data["include"] = []
# Clear the folders that have been deleted
data["include"] = [i for i in data.get("include", []) if os.path.exists(i)]
if preset_path not in data["include"]:
data["include"].append(preset_path)
return data
else:
# Merge the presets
cmake_preset = json.loads(load(preset_path))
for preset_type in ("configurePresets", "buildPresets", "testPresets"):
for preset in cmake_preset.get(preset_type, []):
if preset_type not in data:
data[preset_type] = []
position = _get_already_existing_preset_index(preset["name"], data[preset_type])
if position is not None:
# If the preset already existed, replace the element with the new one
data[preset_type][position] = preset
else:
data[preset_type].append(preset)
return data
def load_cmake_presets(folder):
tmp = load(os.path.join(folder, "CMakePresets.json"))
return json.loads(tmp)
def get_configure_preset(cmake_presets, conanfile):
expected_name = _configure_preset_name(conanfile, multiconfig=False)
# Do we find a preset for the current configuration?
for preset in cmake_presets["configurePresets"]:
if preset["name"] == expected_name:
return preset
expected_name = _configure_preset_name(conanfile, multiconfig=True)
# In case of multi-config generator or None build_type
for preset in cmake_presets["configurePresets"]:
if preset["name"] == expected_name:
return preset
# FIXME: Might be an issue if someone perform several conan install that involves different
# CMake generators (multi and single config). Would be impossible to determine which
# is the correct configurePreset because the generator IS in the configure preset.
raise ConanException("Not available configurePreset, expected name is {}".format(expected_name))