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 @@ -60,7 +60,7 @@ def __init__(self, conanfile):
def is_multi_configuration(self):
return is_multi_configuration(self._generator)

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

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 @@ -140,9 +149,11 @@ def _config_arg(self, build_type):
build_config = "--config {}".format(build_type) if build_type and is_multi else ""
return build_config

def _build(self, build_type=None, target=None, cli_args=None, build_tool_args=None, env="",
def _build(self, build_type=None, target=None, cli_args=None, build_tool_args=None, env="", subfolder=None,
stdout=None, stderr=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 @@ -167,7 +178,7 @@ def _build(self, build_type=None, target=None, cli_args=None, build_tool_args=No
command = "%s --build %s" % (self._cmake_program, arg_list)
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,
def build(self, build_type=None, target=None, cli_args=None, build_tool_args=None, subfolder=None,
stdout=None, stderr=None):
"""

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, subfolder=None, cli_args=None, stdout=None, stderr=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
116 changes: 116 additions & 0 deletions test/functional/toolchains/cmake/test_cmake_multi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
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")

if platform.system() != "Windows":
assert "[100%] Built target hello_one" in client.out
assert "[100%] Built target hello_two" in client.out
assert "multi/0.1: package(): Packaged 1 '.h' file: hello_two.h" in client.out
assert "multi/0.1: package(): Packaged 1 '.a' file: libhello_two.a" 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", "libhello_one.a"))
Copy link
Member

Choose a reason for hiding this comment

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

Make the checks that are the same, common and independent of the if platform


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", "libhello_two.a"))
else:
assert "multi/0.1: package(): Packaged 1 '.h' file: hello_two.h" in client.out
assert "multi/0.1: package(): Packaged 1 '.lib' file: hello_two.lib" 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", "hello_one.lib"))

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", "hello_two.lib"))
Loading