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

new tools.cmake.cmake_layout:build_folder #15767

Merged
merged 9 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 6 additions & 2 deletions conan/tools/cmake/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ def cmake_layout(conanfile, generator=None, src_folder=".", build_folder="build"
except ConanException:
raise ConanException("'build_type' setting not defined, it is necessary for cmake_layout()")

if conanfile._conan_node.recipe in (RECIPE_CONSUMER, RECIPE_EDITABLE):
build_folder = conanfile.conf.get("tools.cmake.cmake_layout:build_folder") or build_folder
build_folder = build_folder if not subproject else os.path.join(subproject, build_folder)
config_build_folder, user_defined_build = get_build_folder_custom_vars(conanfile)
if config_build_folder:
Expand All @@ -54,7 +56,8 @@ def get_build_folder_custom_vars(conanfile):
conanfile_vars = conanfile.folders.build_folder_vars
build_vars = conanfile.conf.get("tools.cmake.cmake_layout:build_folder_vars", check_type=list)
if conanfile.tested_reference_str:
build_vars = build_vars or conanfile_vars or \
if build_vars is None: # The user can define conf build_folder_vars = [] for no vars
build_vars = conanfile_vars or \
["settings.compiler", "settings.compiler.version", "settings.arch",
"settings.compiler.cppstd", "settings.build_type", "options.shared"]
else:
Expand All @@ -63,7 +66,8 @@ def get_build_folder_custom_vars(conanfile):
except AttributeError:
is_consumer = False
if is_consumer:
build_vars = build_vars or conanfile_vars or []
if build_vars is None:
build_vars = conanfile_vars or []
else:
build_vars = conanfile_vars or []

Expand Down
5 changes: 4 additions & 1 deletion conans/client/generators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,10 @@ def relativize_paths(conanfile, placeholder):
return None, None
abs_base_path = os.path.join(abs_base_path, "") # For the trailing / to dissambiguate matches
generators_folder = conanfile.generators_folder
rel_path = os.path.relpath(abs_base_path, generators_folder)
try:
rel_path = os.path.relpath(abs_base_path, generators_folder)
except ValueError: # In case the unit in Windows is different, path cannot be made relative
return None, None
new_path = placeholder if rel_path == "." else os.path.join(placeholder, rel_path)
new_path = os.path.join(new_path, "") # For the trailing / to dissambiguate matches
return abs_base_path, new_path
2 changes: 2 additions & 0 deletions conans/client/profile_loader.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import platform
import tempfile
from collections import OrderedDict, defaultdict

from jinja2 import Environment, FileSystemLoader
Expand Down Expand Up @@ -122,6 +123,7 @@ def _load_profile(self, profile_name, cwd):
file_path = os.path.basename(profile_path)
context = {"platform": platform,
"os": os,
"tempfile": tempfile,
"profile_dir": base_path,
"profile_name": file_path,
"conan_version": conan_version,
Expand Down
1 change: 1 addition & 0 deletions conans/model/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"tools.cmake.cmaketoolchain:toolset_cuda": "(Experimental) Path to a CUDA toolset to use, or version if installed at the system level",
"tools.cmake.cmaketoolchain:presets_environment": "String to define wether to add or not the environment section to the CMake presets. Empty by default, will generate the environment section in CMakePresets. Can take values: 'disabled'.",
"tools.cmake.cmake_layout:build_folder_vars": "Settings and Options that will produce a different build folder and different CMake presets names",
"tools.cmake.cmake_layout:build_folder": "Allow configuring the base folder of the build, for local builds and test_package build folder",
"tools.cmake:cmake_program": "Path to CMake executable",
"tools.cmake:install_strip": "Add --strip to cmake.install()",
"tools.deployer:symlinks": "Set to False to disable deployers copying symlinks",
Expand Down
151 changes: 151 additions & 0 deletions conans/test/integration/layout/test_cmake_build_folder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import os
import re
import textwrap

import pytest

from conans.test.assets.genconanfile import GenConanfile
from conans.test.utils.test_files import temp_folder
from conans.test.utils.tools import TestClient


@pytest.mark.parametrize("cmd", ["install", "build"])
def test_cmake_layout_build_folder(cmd):
""" testing the tools.cmake.cmake_layout:build_folder config for
both build and install commands
"""
c = TestClient()
abs_build = temp_folder()
conanfile = textwrap.dedent("""
from conan import ConanFile
from conan.tools.cmake import cmake_layout

class HelloTestConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
generators = "CMakeToolchain"
def layout(self):
cmake_layout(self, generator="Ninja") # Ninja so same in all OSs, single-config
""")

c.save({"conanfile.py": conanfile})
c.run(f'{cmd} . -c tools.cmake.cmake_layout:build_folder="{abs_build}"')
assert os.path.exists(os.path.join(abs_build, "release", "generators", "conan_toolchain.cmake"))
assert not os.path.exists(os.path.join(c.current_folder, "build"))

# Make sure that a non-existing folder will not fail and will be created
new_folder = os.path.join(temp_folder(), "my build")
c.run(f'{cmd} . -c tools.cmake.cmake_layout:build_folder="{new_folder}"')
assert os.path.exists(os.path.join(new_folder, "release", "generators", "conan_toolchain.cmake"))
assert not os.path.exists(os.path.join(c.current_folder, "build"))

# Just in case we check that local build folder would be created if no arg is provided
c.run('build . ')
assert os.path.exists(os.path.join(c.current_folder, "build"))


@pytest.mark.parametrize("cmd", ["install", "build"])
def test_cmake_layout_build_folder_relative(cmd):
""" Same as above, but with a relative folder, which is relative to the conanfile
"""
c = TestClient()
conanfile = textwrap.dedent("""
from conan import ConanFile
from conan.tools.cmake import cmake_layout

class HelloTestConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
generators = "CMakeToolchain"
def layout(self):
cmake_layout(self, generator="Ninja") # Ninja so same in all OSs, single-config
""")

c.save({"pkg/conanfile.py": conanfile})
c.run(f'{cmd} pkg -c tools.cmake.cmake_layout:build_folder=mybuild')
abs_build = os.path.join(c.current_folder, "pkg", "mybuild")
assert os.path.exists(os.path.join(abs_build, "release", "generators", "conan_toolchain.cmake"))
assert not os.path.exists(os.path.join(c.current_folder, "pkg", "build"))

# relative path, pointing to a sibling folder
c.run(f'{cmd} pkg -c tools.cmake.cmake_layout:build_folder=../mybuild')
abs_build = os.path.join(c.current_folder, "mybuild")
assert os.path.exists(os.path.join(abs_build, "release", "generators", "conan_toolchain.cmake"))
assert not os.path.exists(os.path.join(c.current_folder, "build"))

# Just in case we check that local build folder would be created if no arg is provided
c.run('build pkg ')
assert os.path.exists(os.path.join(c.current_folder, "pkg", "build"))


def test_test_cmake_layout_build_folder_test_package():
""" it also works to relocate the test_package temporary build folders to elsewhere
"""
c = TestClient()
abs_build = temp_folder()
test_conanfile = textwrap.dedent("""
from conan import ConanFile
from conan.tools.cmake import cmake_layout

class HelloTestConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
generators = "CMakeToolchain"

def requirements(self):
self.requires(self.tested_reference_str)

def layout(self):
cmake_layout(self)

def test(self):
pass
""")

c.save({"conanfile.py": GenConanfile("pkg", "0.1"),
"test_package/conanfile.py": test_conanfile})
c.run(f'create . -c tools.cmake.cmake_layout:build_folder="{abs_build}" '
'-c tools.cmake.cmake_layout:build_folder_vars=[]')
assert os.path.exists(os.path.join(abs_build, "generators", "conan_toolchain.cmake"))
assert not os.path.exists(os.path.join(c.current_folder, "test_package", "build"))

c.run(f'create . -c tools.cmake.cmake_layout:build_folder_vars=[]')
assert os.path.exists(os.path.join(c.current_folder, "test_package", "build"))


def test_test_cmake_layout_build_folder_test_package_temp():
""" using always the same test_package build_folder will cause collisions.
We need a mechanism to relocate, still provide unique folders for each build
"""
c = TestClient()
test_conanfile = textwrap.dedent("""
from conan import ConanFile
from conan.tools.cmake import cmake_layout

class HelloTestConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
generators = "CMakeToolchain"

def requirements(self):
self.requires(self.tested_reference_str)

def layout(self):
cmake_layout(self)

def test(self):
pass
""")

profile = textwrap.dedent("""
include(default)
[conf]
tools.cmake.cmake_layout:build_folder={{tempfile.mkdtemp()}}
tools.cmake.cmake_layout:build_folder_vars=[]
""")
c.save({"conanfile.py": GenConanfile("pkg", "0.1"),
"test_package/conanfile.py": test_conanfile,
"profile": profile})
c.run(f'create . -pr=profile')
build_folder = re.search(r"Test package build folder: (\S+)", str(c.out)).group(1)
assert os.path.exists(os.path.join(build_folder, "generators", "conan_toolchain.cmake"))
assert not os.path.exists(os.path.join(c.current_folder, "test_package", "build"))

c.run(f'create . -c tools.cmake.cmake_layout:build_folder_vars=[]')
assert os.path.exists(os.path.join(c.current_folder, "test_package", "build"))