Skip to content
Merged
40 changes: 31 additions & 9 deletions conan/tools/cmake/cmake.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def is_multi_configuration(self):
return is_multi_configuration(self._generator)

def configure(self, variables=None, build_script_folder=None, cli_args=None,
stdout=None, stderr=None):
stdout=None, stderr=None, subfolder=None):
"""

Reads the ``CMakePresets.json`` file generated by the
Expand All @@ -83,6 +83,9 @@ def configure(self, variables=None, build_script_folder=None, cli_args=None,
``self.folders.source`` at the ``layout()`` method.
:param cli_args: List of arguments ``[arg1, arg2, ...]`` that will be passed
as extra CLI arguments to pass to cmake invocation
:param subfolder: (Experimental): The name of a subfolder to be created inside the ``build_folder``
and the ``package_folder``. If not provided, files will be placed
in the ``build_folder`` and the ``package_folder`` root.
:param stdout: Use it to redirect stdout to this stream
:param stderr: Use it to redirect stderr to this stream
"""
Expand All @@ -93,13 +96,18 @@ def configure(self, variables=None, build_script_folder=None, cli_args=None,
cmakelist_folder = cmakelist_folder.replace("\\", "/")

build_folder = self._conanfile.build_folder
if subfolder:
build_folder = os.path.join(self._conanfile.build_folder, subfolder)
mkdir(self._conanfile, build_folder)

arg_list = [self._cmake_program]
if self._generator:
arg_list.append('-G "{}"'.format(self._generator))
if self._toolchain_file:
toolpath = self._toolchain_file.replace("\\", "/")
toolpath = self._toolchain_file
if subfolder:
toolpath = os.path.relpath(self._toolchain_file, start=subfolder)
toolpath = toolpath.replace("\\", "/")
arg_list.append('-DCMAKE_TOOLCHAIN_FILE="{}"'.format(toolpath))
if self._conanfile.package_folder:
pkg_folder = self._conanfile.package_folder.replace("\\", "/")
Expand All @@ -110,6 +118,7 @@ def configure(self, variables=None, build_script_folder=None, cli_args=None,
self._cache_variables.update(variables)

arg_list.extend(['-D{}="{}"'.format(k, v) for k, v in self._cache_variables.items()])

arg_list.append('"{}"'.format(cmakelist_folder))

if not cli_args or ("--log-level" not in cli_args and "--loglevel" not in cli_args):
Expand Down Expand Up @@ -141,8 +150,10 @@ def _config_arg(self, build_type):
return build_config

def _build(self, build_type=None, target=None, cli_args=None, build_tool_args=None, env="",
stdout=None, stderr=None):
stdout=None, stderr=None, subfolder=None):
bf = self._conanfile.build_folder
if subfolder:
bf = os.path.join(self._conanfile.build_folder, subfolder)
build_config = self._config_arg(build_type)

args = []
Expand All @@ -168,7 +179,7 @@ def _build(self, build_type=None, target=None, cli_args=None, build_tool_args=No
self._conanfile.run(command, env=env, stdout=stdout, stderr=stderr)

def build(self, build_type=None, target=None, cli_args=None, build_tool_args=None,
stdout=None, stderr=None):
stdout=None, stderr=None, subfolder=None):
"""

:param build_type: Use it only to override the value defined in the ``settings.build_type``
Expand All @@ -182,13 +193,16 @@ def build(self, build_type=None, target=None, cli_args=None, build_tool_args=Non
:param build_tool_args: A list of arguments ``[barg1, barg2, ...]`` for the underlying
build system that will be passed to the command
line after the ``--`` indicator: ``cmake --build ... -- barg1 barg2``
:param subfolder: (Experimental): The name of a subfolder to be created inside the ``build_folder``
and the ``package_folder``. If not provided, files will be placed
in the ``build_folder`` and the ``package_folder`` root.
:param stdout: Use it to redirect stdout to this stream
:param stderr: Use it to redirect stderr to this stream
"""
self._conanfile.output.info("Running CMake.build()")
self._build(build_type, target, cli_args, build_tool_args, stdout=stdout, stderr=stderr)
self._build(build_type, target, cli_args, build_tool_args, subfolder=subfolder, stdout=stdout, stderr=stderr)

def install(self, build_type=None, component=None, cli_args=None, stdout=None, stderr=None):
def install(self, build_type=None, component=None, cli_args=None, stdout=None, stderr=None, subfolder=None):
"""
Equivalent to running ``cmake --install``

Expand All @@ -199,16 +213,24 @@ def install(self, build_type=None, component=None, cli_args=None, stdout=None, s
not build type.
:param cli_args: A list of arguments ``[arg1, arg2, ...]`` for the underlying build system
that will be passed to the command line: ``cmake --install ... arg1 arg2``
:param subfolder: (Experimental): The name of a subfolder to be created inside the ``build_folder``
and the ``package_folder``. If not provided, files will be placed
in the ``build_folder`` and the ``package_folder`` root.
:param stdout: Use it to redirect stdout to this stream
:param stderr: Use it to redirect stderr to this stream
"""
self._conanfile.output.info("Running CMake.install()")
mkdir(self._conanfile, self._conanfile.package_folder)
package_folder = self._conanfile.package_folder
build_folder = self._conanfile.build_folder
if subfolder:
package_folder = os.path.join(self._conanfile.package_folder, subfolder)
build_folder = os.path.join(self._conanfile.build_folder, subfolder)
mkdir(self._conanfile, package_folder)

build_config = self._config_arg(build_type)

pkg_folder = '"{}"'.format(self._conanfile.package_folder.replace("\\", "/"))
build_folder = '"{}"'.format(self._conanfile.build_folder)
pkg_folder = '"{}"'.format(package_folder.replace("\\", "/"))
build_folder = '"{}"'.format(build_folder.replace("\\", "/"))
arg_list = ["--install", build_folder, build_config, "--prefix", pkg_folder]
if component:
arg_list.extend(["--component", component])
Expand Down
102 changes: 102 additions & 0 deletions test/functional/toolchains/cmake/test_cmake_multi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import os
import platform
import textwrap

from conan.test.utils.tools import TestClient


def test_multi_cMake():
conanfile = textwrap.dedent("""
from conan import ConanFile
from conan.tools.cmake import CMakeToolchain, CMake, cmake_layout, CMakeDeps
import os


class multiRecipe(ConanFile):
settings = "os", "compiler", "build_type", "arch"

exports_sources = "cmake_one/CMakeLists.txt", "cmake_two/CMakeLists.txt", "src_one/*", "src_two/*"

def layout(self):
cmake_layout(self)

def generate(self):
deps = CMakeDeps(self)
deps.generate()
tc = CMakeToolchain(self)
tc.generate()

def build_one(self):
cmake = CMake(self)
cmake.configure(build_script_folder="cmake_one", subfolder="one")
cmake.build(subfolder="one")
# cmake.install(subfolder="one")

def build_two(self):
cmake = CMake(self)
# CMAKE_PREFIX_PATH
cmake.configure(build_script_folder="cmake_two", subfolder="two")
cmake.build(subfolder="two")

def build(self):
self.build_one()
self.build_two()

def package(self):
cmake = CMake(self)
cmake.install(subfolder="two")

def package_info(self):
self.cpp_info.libs = ["hello_two"]
self.cpp_info.includedirs = ['two/include']
self.cpp_info.libdirs = ['two/lib']
""")

hello_cpp = textwrap.dedent("""
#include <iostream>
#include "hello_{name}.h"

void hello_{name}() {{
std::cout << "Hello, World {name}!" << std::endl;
}}
""")

hello_h = textwrap.dedent("""
#ifndef HELLO_{name}_H
#define HELLO_{name}_H

void hello_{name}();

#endif
""")

cmakelist = textwrap.dedent("""
cmake_minimum_required(VERSION 3.15)
project(hello_{name} LANGUAGES CXX)

add_library(hello_{name} ../src_{name}/hello_{name}.cpp)
target_include_directories(hello_{name} PUBLIC ../src_{name})

set_target_properties(hello_{name} PROPERTIES PUBLIC_HEADER "../src_{name}/hello_{name}.h")
install(TARGETS hello_{name})
""")

client = TestClient()
client.save({"conanfile.py": conanfile,
"cmake_one/CMakeLists.txt": cmakelist.format(name="one"),
"cmake_two/CMakeLists.txt": cmakelist.format(name="two"),
"src_one/hello_one.h": hello_h.format(name="one"),
"src_one/hello_one.cpp": hello_cpp.format(name="one"),
"src_two/hello_two.h": hello_h.format(name="two"),
"src_two/hello_two.cpp": hello_cpp.format(name="two")})

client.run("create . --name=multi --version=0.1")
file_ext = '.a' if platform.system() != "Windows" else '.lib'

assert "multi/0.1: package(): Packaged 1 '.h' file: hello_two.h" in client.out
assert f"multi/0.1: package(): Packaged 1 '{file_ext}' file: libhello_two{file_ext}" in client.out
package_folder = client.created_layout().package()
assert not os.path.exists(os.path.join(package_folder, "one", "include", "hello_one.h"))
assert not os.path.exists(os.path.join(package_folder, "one", "lib", f"libhello_one{file_ext}"))
assert os.path.exists(os.path.join(package_folder, "two", "include", "hello_two.h"))
assert os.path.exists(os.path.join(package_folder, "two", "lib", f"libhello_two{file_ext}"))
Loading