Skip to content

Commit

Permalink
cmake presets with VS and ninja and improved merge (#11666)
Browse files Browse the repository at this point in the history
* tests pending

* Added tests

* Fix default

* not cpp 20

* Update conan/tools/cmake/presets.py

Co-authored-by: James <james@conan.io>

* fix

* WIP

* Fix win test

* Tested win

* toolset and architecture for Ninka

* Fix bug duplicated

* Fixed bug

* comment

* Test ninja cmake presets

* version only for non windows

* compiler 191

* CMakePresets bugfixes related to the update of a configuration

Co-authored-by: James <james@conan.io>
  • Loading branch information
lasote and memsharded committed Jul 22, 2022
1 parent 2ca4b72 commit 6a6ff6e
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 22 deletions.
80 changes: 59 additions & 21 deletions conan/tools/cmake/presets.py
Expand Up @@ -4,11 +4,12 @@

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 _add_build_preset(conanfile, multiconfig):
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),
Expand Down Expand Up @@ -42,7 +43,7 @@ def _configure_preset_name(conanfile, multiconfig):
return str(build_type).lower()


def _add_configure_preset(conanfile, generator, cache_variables, toolchain_file, multiconfig):
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:
Expand All @@ -53,8 +54,26 @@ def _add_configure_preset(conanfile, generator, cache_variables, toolchain_file,
"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:
Expand Down Expand Up @@ -102,8 +121,8 @@ def _contents(conanfile, toolchain_file, cache_variables, generator):
"testPresets": []
}
multiconfig = is_multi_configuration(generator)
ret["buildPresets"].append(_add_build_preset(conanfile, multiconfig))
_conf = _add_configure_preset(conanfile, generator, cache_variables, toolchain_file, multiconfig)
ret["buildPresets"].append(_build_preset(conanfile, multiconfig))
_conf = _configure_preset(conanfile, generator, cache_variables, toolchain_file, multiconfig)
ret["configurePresets"].append(_conf)
return ret

Expand All @@ -126,23 +145,22 @@ def write_cmake_presets(conanfile, toolchain_file, generator, cache_variables):
multiconfig = is_multi_configuration(generator)

if os.path.exists(preset_path):
# We append the new configuration making sure that we don't overwrite it
data = json.loads(load(preset_path))
if multiconfig:
new_build_preset_name = _build_preset_name(conanfile)
already_exist = any([b["name"] for b in data["buildPresets"]
if b["name"] == new_build_preset_name])
if not already_exist:
data["buildPresets"].append(_add_build_preset(conanfile, multiconfig))
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:
new_configure_preset_name = _configure_preset_name(conanfile, multiconfig)
already_exist = any([c["name"] for c in data["configurePresets"]
if c["name"] == new_configure_preset_name])
if not already_exist:
conf_preset = _add_configure_preset(conanfile, generator, cache_variables,
toolchain_file, multiconfig)
data["configurePresets"].append(conf_preset)
data["buildPresets"].append(_add_build_preset(conanfile, multiconfig))
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)

Expand Down Expand Up @@ -173,7 +191,21 @@ def save_cmake_user_presets(conanfile, preset_path):
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"] = []
Expand All @@ -189,7 +221,13 @@ def _append_user_preset_path(conanfile, data, preset_path):
for preset in cmake_preset.get(preset_type, []):
if preset_type not in data:
data[preset_type] = []
data[preset_type].append(preset)

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


Expand Down
108 changes: 107 additions & 1 deletion conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
Expand Up @@ -9,6 +9,7 @@
from conan.tools.cmake.presets import load_cmake_presets
from conans.test.assets.genconanfile import GenConanfile
from conans.test.utils.tools import TestClient
from conans.util.files import rmdir


def test_cross_build():
Expand Down Expand Up @@ -384,7 +385,8 @@ def test_cmake_presets_multiconfig():
assert presets["buildPresets"][0]["configuration"] == "Release"
assert presets["buildPresets"][1]["configuration"] == "Debug"

client.run("install mylib/1.0@ -g CMakeToolchain -s build_type=RelWithDebInfo --profile:h=profile")
client.run("install mylib/1.0@ -g CMakeToolchain -s build_type=RelWithDebInfo "
"--profile:h=profile")
client.run("install mylib/1.0@ -g CMakeToolchain -s build_type=MinSizeRel --profile:h=profile")
presets = json.loads(client.load("CMakePresets.json"))
assert len(presets["buildPresets"]) == 4
Expand Down Expand Up @@ -581,3 +583,107 @@ def layout(self):
assert "build/17/generators/conan_toolchain.cmake" \
in presets["configurePresets"][1]["cacheVariables"]["CMAKE_TOOLCHAIN_FILE"].replace("\\",
"/")


def test_presets_updated():
"""If a preset file is generated more than once, the values are always added and, in case the
configurePreset or buildPreset already exist, the new preset is updated """
client = TestClient()
conanfile = textwrap.dedent("""
from conan import ConanFile
from conan.tools.cmake import cmake_layout
class Conan(ConanFile):
settings = "os", "arch", "compiler", "build_type"
generators = "CMakeToolchain"
def layout(self):
cmake_layout(self)
""")
client.save({"conanfile.py": conanfile, "CMakeLists.txt": "foo"})
configs = ["-c tools.cmake.cmaketoolchain.presets:max_schema_version=2 ",
"-c tools.cmake.cmake_layout:build_folder_vars='[\"settings.compiler.cppstd\"]'"]
client.run("install . {} -s compiler.cppstd=14".format(" ".join(configs)))
client.run("install . {} -s compiler.cppstd=17".format(" ".join(configs)))

presets = json.loads(client.load("CMakeUserPresets.json"))
assert len(presets["configurePresets"]) == 2
assert "FOO" not in presets["configurePresets"][0]["cacheVariables"]

# Now introduce a cache_variable FOO to see if we get it in the CMakeUserPresets.json (that
# at the same time, it will come from the build/xxxx/CMakePreset.json that is also updated)
conanfile = textwrap.dedent("""
from conan import ConanFile
from conan.tools.cmake import cmake_layout, CMakeToolchain
class Conan(ConanFile):
settings = "os", "arch", "compiler", "build_type"
def generate(self):
tc = CMakeToolchain(self)
tc.cache_variables["FOO"] = "var"
tc.generate()
def layout(self):
cmake_layout(self)
""")
client.save({"conanfile.py": conanfile})
client.run("install . {} -s compiler.cppstd=14".format(" ".join(configs)))
presets = json.loads(client.load("CMakeUserPresets.json"))
assert len(presets["configurePresets"]) == 2
assert "FOO" in presets["configurePresets"][0]["cacheVariables"]


@pytest.mark.parametrize("arch, arch_toolset", [("x86", "x86_64"), ("x86_64", "x86_64")])
def test_presets_ninja_msvc(arch, arch_toolset):
client = TestClient()
conanfile = textwrap.dedent("""
from conan import ConanFile
from conan.tools.cmake import cmake_layout
class Conan(ConanFile):
settings = "os", "arch", "compiler", "build_type"
generators = "CMakeToolchain"
def layout(self):
cmake_layout(self)
""")
client.save({"conanfile.py": conanfile, "CMakeLists.txt": "foo"})
configs = ["-c tools.cmake.cmaketoolchain:toolset_arch={}".format(arch_toolset),
"-c tools.cmake.cmake_layout:build_folder_vars='[\"settings.compiler.cppstd\"]'",
"-c tools.cmake.cmaketoolchain:generator=Ninja"]
msvc = " -s compiler=msvc -s compiler.version=191 -s compiler.runtime=static " \
"-s compiler.runtime_type=Release"
client.run("install . {} -s compiler.cppstd=14 {} -s arch={}".format(" ".join(configs), msvc, arch))

presets = json.loads(client.load("build/14/generators/CMakePresets.json"))

toolset_value = {"x86_64": "host=x86_64", "x86": "x86"}.get(arch_toolset)
arch_value = {"x86_64": "x64", "x86": "x86"}.get(arch)

assert presets["configurePresets"][0]["architecture"]["value"] == arch_value
assert presets["configurePresets"][0]["architecture"]["strategy"] == "external"
assert presets["configurePresets"][0]["toolset"]["value"] == toolset_value
assert presets["configurePresets"][0]["toolset"]["strategy"] == "external"

# Only for Ninja, no ninja, no values
rmdir(os.path.join(client.current_folder, "build"))
configs = ["-c tools.cmake.cmaketoolchain:toolset_arch={}".format(arch_toolset),
"-c tools.cmake.cmake_layout:build_folder_vars='[\"settings.compiler.cppstd\"]'"]
client.run(
"install . {} -s compiler.cppstd=14 {} -s arch={}".format(" ".join(configs), msvc, arch))

presets = json.loads(client.load("build/14/generators/CMakePresets.json"))
assert "architecture" not in presets["configurePresets"][0]
assert "toolset" not in presets["configurePresets"][0]

# No toolset defined in conf, no value
rmdir(os.path.join(client.current_folder, "build"))
configs = ["-c tools.cmake.cmake_layout:build_folder_vars='[\"settings.compiler.cppstd\"]'",
"-c tools.cmake.cmaketoolchain:generator=Ninja"]

client.run(
"install . {} -s compiler.cppstd=14 {} -s arch={}".format(" ".join(configs), msvc, arch))
presets = json.loads(client.load("build/14/generators/CMakePresets.json"))
assert "architecture" in presets["configurePresets"][0]
assert "toolset" not in presets["configurePresets"][0]

0 comments on commit 6a6ff6e

Please sign in to comment.