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

[DRAFT] Add environment to presets inheriting from other presets #15491

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 27 additions & 20 deletions conan/tools/cmake/presets.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@


def write_cmake_presets(conanfile, toolchain_file, generator, cache_variables,
user_presets_path=None, preset_prefix=None, buildenv=None, runenv=None):
user_presets_path=None, preset_prefix=None):
preset_path, preset_data = _CMakePresets.generate(conanfile, toolchain_file, generator,
cache_variables, preset_prefix, buildenv, runenv)
cache_variables, preset_prefix)
_IncludingPresets.generate(conanfile, preset_path, user_presets_path, preset_prefix, preset_data)


class _CMakePresets:
""" Conan generated main CMakePresets.json inside the generators_folder
"""
@staticmethod
def generate(conanfile, toolchain_file, generator, cache_variables, preset_prefix, buildenv, runenv):
def generate(conanfile, toolchain_file, generator, cache_variables, preset_prefix):
cache_variables = cache_variables or {}
if platform.system() == "Windows" and generator == "MinGW Makefiles":
if "CMAKE_SH" not in cache_variables:
Expand Down Expand Up @@ -55,18 +55,17 @@ def generate(conanfile, toolchain_file, generator, cache_variables, preset_prefi
if os.path.exists(preset_path) and multiconfig:
data = json.loads(load(preset_path))
build_preset = _CMakePresets._build_preset_fields(conanfile, multiconfig, preset_prefix)
test_preset = _CMakePresets._test_preset_fields(conanfile, multiconfig, preset_prefix,
runenv)
test_preset = _CMakePresets._test_preset_fields(conanfile, multiconfig, preset_prefix)
_CMakePresets._insert_preset(data, "buildPresets", build_preset)
_CMakePresets._insert_preset(data, "testPresets", test_preset)
configure_preset = _CMakePresets._configure_preset(conanfile, generator, cache_variables,
toolchain_file, multiconfig,
preset_prefix, buildenv)
preset_prefix)
# Conan generated presets should have only 1 configurePreset, no more, overwrite it
data["configurePresets"] = [configure_preset]
else:
data = _CMakePresets._contents(conanfile, toolchain_file, cache_variables, generator,
preset_prefix, buildenv, runenv)
preset_prefix)

preset_content = json.dumps(data, indent=4)
save(preset_path, preset_content)
Expand All @@ -84,29 +83,35 @@ def _insert_preset(data, preset_type, preset):
data[preset_type].append(preset)

@staticmethod
def _contents(conanfile, toolchain_file, cache_variables, generator, preset_prefix, buildenv,
runenv):
def _contents(conanfile, toolchain_file, cache_variables, generator, preset_prefix):
"""
Contents for the CMakePresets.json
It uses schema version 3 unless it is forced to 2
"""
add_env = conanfile.conf.get("tools.cmake.cmaketoolchain:presets_environment", default="",
check_type=str, choices=("disabled", "")) != "disabled"
multiconfig = is_multi_configuration(generator)
conf = _CMakePresets._configure_preset(conanfile, generator, cache_variables, toolchain_file,
multiconfig, preset_prefix, buildenv)
multiconfig, preset_prefix, add_env)
build = _CMakePresets._build_preset_fields(conanfile, multiconfig, preset_prefix)
test = _CMakePresets._test_preset_fields(conanfile, multiconfig, preset_prefix, runenv)
ret = {"version": 3,
test = _CMakePresets._test_preset_fields(conanfile, multiconfig, preset_prefix, add_env)

ret = {"version": 4 if add_env else 3,
"vendor": {"conan": {}},
"cmakeMinimumRequired": {"major": 3, "minor": 15, "patch": 0},
"configurePresets": [conf],
"buildPresets": [build],
"testPresets": [test]
}

if add_env:
ret.update({"include": [os.path.join(conanfile.generators_folder, "conan-build-presets.json"),
os.path.join(conanfile.generators_folder, "conan-run-presets.json")]})
return ret

@staticmethod
def _configure_preset(conanfile, generator, cache_variables, toolchain_file, multiconfig,
preset_prefix, buildenv):
preset_prefix, add_env=False):
build_type = conanfile.settings.get_safe("build_type")
name = _CMakePresets._configure_preset_name(conanfile, multiconfig)
if preset_prefix:
Expand All @@ -115,14 +120,12 @@ def _configure_preset(conanfile, generator, cache_variables, toolchain_file, mul
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 buildenv:
ret["environment"] = buildenv
if add_env:
ret.update({"inherits": _CMakePresets.environment_preset_name(conanfile, "build")})

if is_msvc(conanfile):
# We can force the generator Visual even if it is Ninja, to define the toolset
Expand Down Expand Up @@ -195,12 +198,16 @@ def _build_preset_fields(conanfile, multiconfig, preset_prefix):
return ret

@staticmethod
def _test_preset_fields(conanfile, multiconfig, preset_prefix, runenv):
def _test_preset_fields(conanfile, multiconfig, preset_prefix, add_env=False):
ret = _CMakePresets._common_preset_fields(conanfile, multiconfig, preset_prefix)
if runenv:
ret["environment"] = runenv
if add_env:
ret.update({"inherits": _CMakePresets.environment_preset_name(conanfile, "run")})
return ret

@staticmethod
def environment_preset_name(conanfile, scope):
return "environment-" + _CMakePresets._build_and_test_preset_name(conanfile) + "-" + scope

@staticmethod
def _build_and_test_preset_name(conanfile):
build_type = conanfile.settings.get_safe("build_type")
Expand Down
15 changes: 1 addition & 14 deletions conan/tools/cmake/toolchain/toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,21 +215,8 @@ def generate(self):
else:
cache_variables[name] = value

buildenv, runenv = None, None

if self._conanfile.conf.get("tools.cmake.cmaketoolchain:presets_environment", default="",
check_type=str, choices=("disabled", "")) != "disabled":

build_env = VirtualBuildEnv(self._conanfile, auto_generate=True).vars()
run_env = VirtualRunEnv(self._conanfile, auto_generate=True).vars()

buildenv = {name: value for name, value in
build_env.items(variable_reference="$penv{{{name}}}")}
runenv = {name: value for name, value in
run_env.items(variable_reference="$penv{{{name}}}")}

write_cmake_presets(self._conanfile, toolchain, self.generator, cache_variables,
self.user_presets_path, self.presets_prefix, buildenv, runenv)
self.user_presets_path, self.presets_prefix)

def _get_generator(self, recipe_generator):
# Returns the name of the generator to be used by CMake
Expand Down
10 changes: 10 additions & 0 deletions conan/tools/env/environment.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import os
import textwrap
from collections import OrderedDict
Expand Down Expand Up @@ -508,6 +509,12 @@ def save_sh(self, file_location, generate_deactivate=True):
content = f'script_folder="{os.path.abspath(filepath)}"\n' + content
save(file_location, content)

def save_json(self, file_location):
env_vars = EnvVars(self._conanfile, self._values, self._scope)
env = {name: value for name, value in env_vars.items(variable_reference="$penv{{{name}}}")}
content = json.dumps(env, indent=1)
save(file_location, content)

def save_script(self, filename):
"""
Saves a script file (bat, sh, ps1) with a launcher to set the environment.
Expand Down Expand Up @@ -540,7 +547,10 @@ def save_script(self, filename):
self.save_sh(path)

if self._scope:
json_path = os.path.splitext(path)[0] + ".json"
self.save_json(json_path)
register_env_script(self._conanfile, path, self._scope)
register_env_script(self._conanfile, json_path, self._scope)


class ProfileEnvironment:
Expand Down
20 changes: 12 additions & 8 deletions conan/tools/env/virtualbuildenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class VirtualBuildEnv:
"""

def __init__(self, conanfile, auto_generate=False):
self._buildenv = None
self._conanfile = conanfile
if not auto_generate:
self._conanfile.virtualbuildenv = False
Expand Down Expand Up @@ -42,33 +43,36 @@ def environment(self):

:return: an ``Environment`` object instance containing the obtained variables.
"""
# FIXME: Cache value?
build_env = Environment()

if self._buildenv is None:
self._buildenv = Environment()
else:
return self._buildenv

# Top priority: profile
profile_env = self._conanfile.buildenv
build_env.compose_env(profile_env)
self._buildenv.compose_env(profile_env)

build_requires = self._conanfile.dependencies.build.topological_sort
for require, build_require in reversed(build_requires.items()):
if require.direct: # Only buildenv_info from direct deps is propagated
# higher priority, explicit buildenv_info
if build_require.buildenv_info:
build_env.compose_env(build_require.buildenv_info)
self._buildenv.compose_env(build_require.buildenv_info)
# Lower priority, the runenv of all transitive "requires" of the build requires
if build_require.runenv_info:
build_env.compose_env(build_require.runenv_info)
self._buildenv.compose_env(build_require.runenv_info)
# Then the implicit
os_name = self._conanfile.settings_build.get_safe("os")
build_env.compose_env(runenv_from_cpp_info(build_require, os_name))
self._buildenv.compose_env(runenv_from_cpp_info(build_require, os_name))

# Requires in host context can also bring some direct buildenv_info
host_requires = self._conanfile.dependencies.host.topological_sort
for require in reversed(host_requires.values()):
if require.buildenv_info:
build_env.compose_env(require.buildenv_info)
self._buildenv.compose_env(require.buildenv_info)

return build_env
return self._buildenv

def vars(self, scope="build"):
"""
Expand Down
16 changes: 10 additions & 6 deletions conan/tools/env/virtualrunenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def __init__(self, conanfile, auto_generate=False):

:param conanfile: The current recipe object. Always use ``self``.
"""
self._runenv = None
self._conanfile = conanfile
if not auto_generate:
self._conanfile.virtualrunenv = False
Expand All @@ -60,23 +61,26 @@ def environment(self):

:return: an ``Environment`` object instance containing the obtained variables.
"""
runenv = Environment()

if self._runenv is None:
self._runenv = Environment()
else:
return self._runenv

# Top priority: profile
profile_env = self._conanfile.runenv
runenv.compose_env(profile_env)
# FIXME: Cache value?
self._runenv.compose_env(profile_env)

host_req = self._conanfile.dependencies.host
test_req = self._conanfile.dependencies.test
for require, dep in list(host_req.items()) + list(test_req.items()):
if dep.runenv_info:
runenv.compose_env(dep.runenv_info)
self._runenv.compose_env(dep.runenv_info)
if require.run: # Only if the require is run (shared or application to be run)
_os = self._conanfile.settings.get_safe("os")
runenv.compose_env(runenv_from_cpp_info(dep, _os))
self._runenv.compose_env(runenv_from_cpp_info(dep, _os))

return runenv
return self._runenv

def vars(self, scope="run"):
"""
Expand Down
33 changes: 32 additions & 1 deletion conans/client/generators/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import inspect
import json
import os
import traceback
import importlib
Expand Down Expand Up @@ -121,7 +122,6 @@ def write_generators(conanfile, app):
from conan.tools.env import VirtualRunEnv
env = VirtualRunEnv(conanfile)
env.generate()

_generate_aggregated_env(conanfile)

hook_manager.execute("post_generate", conanfile=conanfile)
Expand All @@ -142,6 +142,8 @@ def _receive_conf(conanfile):

def _generate_aggregated_env(conanfile):

from conan.tools.cmake.presets import _CMakePresets

def deactivates(filenames):
# FIXME: Probably the order needs to be reversed
result = []
Expand All @@ -156,6 +158,7 @@ def deactivates(filenames):
bats = []
shs = []
ps1s = []
jsons = []
for env_script in env_scripts:
path = os.path.join(conanfile.generators_folder, env_script)
# Only the .bat and .ps1 are made relative to current script
Expand All @@ -164,6 +167,8 @@ def deactivates(filenames):
bats.append("%~dp0/"+path)
elif env_script.endswith(".sh"):
shs.append(subsystem_path(subsystem, path))
elif env_script.endswith(".json"):
jsons.append(subsystem_path(subsystem, path))
elif env_script.endswith(".ps1"):
path = os.path.relpath(path, conanfile.generators_folder)
# This $PSScriptRoot uses the current script directory
Expand All @@ -176,6 +181,32 @@ def sh_content(files):
save(os.path.join(conanfile.generators_folder, filename), sh_content(shs))
save(os.path.join(conanfile.generators_folder, "deactivate_{}".format(filename)),
sh_content(deactivates(shs)))
if jsons:
def jsons_content(files):
combined_environment = {}
for file in files:
with open(file, 'r') as f:
file_content = f.read()
file_json = json.loads(file_content)
combined_environment.update(file_json)
preset_type = "testPresets" if group == "run" else "configurePresets"
preset_name = _CMakePresets.environment_preset_name(conanfile, group)

preset = {
"version": 4,
preset_type: [
{
"vendor": {"conan": dict()},
"name": preset_name,
"hidden": True,
"environment": combined_environment,
}
]
}
return json.dumps(preset, indent=4)
filename = "conan-{}-presets.json".format(group)
generated.append(filename)
save(os.path.join(conanfile.generators_folder, filename), jsons_content(jsons))
if bats:
def bat_content(files):
return "\r\n".join(["@echo off"] + ['call "{}"'.format(b) for b in files])
Expand Down
Loading