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

cmake presets with VS and ninja and improved merge #11666

Merged
merged 23 commits into from Jul 22, 2022
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
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):
Copy link
Contributor Author

@lasote lasote Jul 19, 2022

Choose a reason for hiding this comment

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

this is only a rename

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):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

rename only

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)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The below code was a bug, previously I only appended a preset if it didn't exist, but it might be changed, it is needed to overwrite it, so now I search the index and update.

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]