diff --git a/README.md b/README.md index 8165cdb2abc..9c27a1ad2ed 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ OK To run specific tests, you can specify the test name too, something like: ```bash -$ python -m pytest conans/test/unittests/client/cmd/export_test.py::ExportTest::test_export_warning -s +$ python -m pytest conans/test/functional/command/export_test.py::TestRevisionModeSCM::test_revision_mode_scm -s ``` The `-s` argument can be useful to see some output that otherwise is captured by *pytest*. diff --git a/conan/tools/meson/toolchain.py b/conan/tools/meson/toolchain.py index d1c2ac0c1be..ac69de98a3e 100644 --- a/conan/tools/meson/toolchain.py +++ b/conan/tools/meson/toolchain.py @@ -39,6 +39,15 @@ class MesonToolchain(object): {{it}} = {{value}} {% endfor %} + {% for subproject, listkeypair in subproject_options -%} + [{{subproject}}:project options] + {% for keypair in listkeypair -%} + {% for it, value in keypair.items() -%} + {{it}} = {{value}} + {% endfor %} + {% endfor %} + {% endfor %} + [binaries] {% if c %}c = {{c}}{% endif %} {% if cpp %}cpp = {{cpp}}{% endif %} @@ -146,6 +155,8 @@ def __init__(self, conanfile, backend=None): # Add all the default dirs self.project_options.update(self._get_default_dirs()) + self.subproject_options = {} + #: Defines the Meson ``pkg_config_path`` variable self.pkg_config_path = self._conanfile.generators_folder #: Defines the Meson ``build.pkg_config_path`` variable (build context) @@ -393,11 +404,19 @@ def _sanitize_format(v): if self.gcc_cxx11_abi: self.cpp_args.append("-D{}".format(self.gcc_cxx11_abi)) + subproject_options = {} + for subproject, listkeypair in self.subproject_options.items(): + if listkeypair is not None and listkeypair is not []: + subproject_options[subproject] = [] + for keypair in listkeypair: + subproject_options[subproject].append({k: to_meson_value(v) for k, v in keypair.items()}) + return { # https://mesonbuild.com/Machine-files.html#properties "properties": {k: to_meson_value(v) for k, v in self.properties.items()}, # https://mesonbuild.com/Machine-files.html#project-specific-options "project_options": {k: to_meson_value(v) for k, v in self.project_options.items()}, + "subproject_options": subproject_options.items(), # https://mesonbuild.com/Builtin-options.html#directories # https://mesonbuild.com/Machine-files.html#binaries # https://mesonbuild.com/Reference-tables.html#compiler-and-linker-selection-variables diff --git a/conans/test/functional/toolchains/meson/test_subproject.py b/conans/test/functional/toolchains/meson/test_subproject.py new file mode 100644 index 00000000000..b225f088404 --- /dev/null +++ b/conans/test/functional/toolchains/meson/test_subproject.py @@ -0,0 +1,186 @@ +import os +import textwrap + +import pytest + +from conans.test.assets.sources import gen_function_c +from conans.test.utils.tools import TestClient + +_conanfile_py = textwrap.dedent(""" + import os + import shutil + from conan import ConanFile + from conan.tools.meson import Meson, MesonToolchain + + class App(ConanFile): + settings = "os", "arch", "compiler", "build_type" + options = {"shared": [True, False], "fPIC": [True, False], "use_french": ['true', 'false']} + default_options = {"shared": False, "fPIC": True, "use_french": 'false'} + exports_sources = "**" + + def config_options(self): + if self.settings.os == "Windows": + self.options.rm_safe("fPIC") + + def configure(self): + if self.options.shared: + self.options.rm_safe("fPIC") + + def layout(self): + self.folders.build = "build" + + def generate(self): + tc = MesonToolchain(self) + tc.subproject_options["hello"] = [{'french': str(self.options.use_french)}] + tc.generate() + + def build(self): + meson = Meson(self) + meson.configure() + meson.build() + + def package(self): + meson = Meson(self) + meson.install() + # https://mesonbuild.com/FAQ.html#why-does-building-my-project-with-msvc-output-static-libraries-called-libfooa + if self.settings.compiler == 'msvc' and not self.options.shared: + shutil.move(os.path.join(self.package_folder, "lib", "libhello.a"), + os.path.join(self.package_folder, "lib", "hello.lib")) + shutil.move(os.path.join(self.package_folder, "lib", "libgreeter.a"), + os.path.join(self.package_folder, "lib", "greeter.lib")) + + def package_info(self): + self.cpp_info.components["hello"].libs = ['hello'] + self.cpp_info.components["greeter"].libs = ['greeter'] + self.cpp_info.components["greeter"].requires = ['hello'] + """) + +_meson_build = textwrap.dedent(""" + project('greeter', 'c') + + hello_proj = subproject('hello') + hello_dep = hello_proj.get_variable('hello_dep') + + inc = include_directories('include') + greeter = static_library('greeter', + 'greeter.c', + include_directories : inc, + dependencies : hello_dep, + install : true) + + install_headers('greeter.h') + """) + +_meson_subproject_build = textwrap.dedent(""" + project('hello', 'c') + + hello_c_args = [] + if get_option('french') + hello_c_args = ['-DFRENCH'] + endif + + inc = include_directories('include') + hello = static_library('hello', + 'hello.c', + include_directories : inc, + c_args: hello_c_args, + install : true) + + hello_dep = declare_dependency(include_directories : inc, link_with : hello) + """) + +_meson_subproject_options = textwrap.dedent(""" + option('french', type : 'boolean', value : false) + """) + +_hello_c = textwrap.dedent(""" + #include + + void hello(void) { + #ifdef FRENCH + printf("Le sous-projet vous salut\\n"); + #else + printf("Hello from subproject\\n"); + #endif + } + """) + +_greeter_c = textwrap.dedent(""" + #include + + void greeter(void) { + hello(); + } + """) + +_hello_h = textwrap.dedent(""" + #pragma once + void hello(void); + """) + +_greeter_h = textwrap.dedent(""" + #pragma once + void greeter(void); + """) + +_test_package_conanfile_py = textwrap.dedent(""" + import os + from conan import ConanFile + from conan.tools.cmake import CMake, cmake_layout + from conan.tools.build import cross_building + + class TestConan(ConanFile): + settings = "os", "compiler", "build_type", "arch" + generators = "CMakeToolchain", "CMakeDeps" + + def requirements(self): + self.requires(self.tested_reference_str) + + def layout(self): + cmake_layout(self) + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def test(self): + if not cross_building(self): + cmd = os.path.join(self.cpp.build.bindirs[0], "test_package") + self.run(cmd, env=["conanrunenv"]) + """) + +_test_package_cmake_lists = textwrap.dedent(""" + cmake_minimum_required(VERSION 3.1) + project(test_package C) + + add_executable(${PROJECT_NAME} src/test_package.c) + find_package(greeter CONFIG REQUIRED) + target_link_libraries(${PROJECT_NAME} greeter::greeter) + """) + + +@pytest.mark.tool("cmake") +@pytest.mark.tool("meson") +def test_subproject(): + client = TestClient() + test_package_c = gen_function_c(name="main", includes=["greeter"], calls=["greeter"]) + client.save({"conanfile.py": _conanfile_py, + "meson.build": _meson_build, + "greeter.c": _greeter_c, + "greeter.h": _greeter_h, + os.path.join("include", "greeter.h"): _greeter_h, + os.path.join("subprojects", "hello", "include", "hello.h"): _hello_h, + os.path.join("subprojects", "hello", "hello.c"): _hello_c, + os.path.join("subprojects", "hello", "meson.build"): _meson_subproject_build, + os.path.join("subprojects", "hello", "meson_options.txt"): _meson_subproject_options, + os.path.join("test_package", "conanfile.py"): _test_package_conanfile_py, + os.path.join("test_package", "CMakeLists.txt"): _test_package_cmake_lists, + os.path.join("test_package", "src", "test_package.c"): test_package_c}) + client.run("create . --name=greeter --version=0.1") + assert "Hello from subproject" in client.out + assert "Le sous-projet vous salut" not in client.out + # Using subproject options + client.run("create . --name=greeter --version=0.1 -o use_french='true'") + assert "Hello from subproject" not in client.out + assert "Le sous-projet vous salut" in client.out diff --git a/conans/test/integration/toolchains/meson/test_mesontoolchain.py b/conans/test/integration/toolchains/meson/test_mesontoolchain.py index 80650b1d361..6ce4b163f13 100644 --- a/conans/test/integration/toolchains/meson/test_mesontoolchain.py +++ b/conans/test/integration/toolchains/meson/test_mesontoolchain.py @@ -455,3 +455,28 @@ def build(self): }) client.run("export tool") client.run("create consumer -pr:h host -pr:b build --build=missing") + + +def test_subproject_options(): + t = TestClient() + conanfile = textwrap.dedent(""" + from conan import ConanFile + from conan.tools.meson import MesonToolchain + class Pkg(ConanFile): + settings = "os", "compiler", "arch", "build_type" + def generate(self): + tc = MesonToolchain(self) + tc.subproject_options["subproject1"] = [{"option1": "enabled"}, {"option2": "disabled"}] + tc.subproject_options["subproject2"] = [{"option3": "enabled"}] + tc.subproject_options["subproject2"].append({"option4": "disabled"}) + tc.generate() + """) + t.save({"conanfile.py": conanfile}) + t.run("install .") + content = t.load(MesonToolchain.native_filename) + assert "[subproject1:project options]" in content + assert "[subproject2:project options]" in content + assert "option1 = 'enabled'" in content + assert "option2 = 'disabled'" in content + assert "option3 = 'enabled'" in content + assert "option4 = 'disabled'" in content