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

Feature/meson subproject #15916

Merged
merged 11 commits into from
Mar 25, 2024
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,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*.
Expand Down
19 changes: 19 additions & 0 deletions conan/tools/meson/toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
186 changes: 186 additions & 0 deletions conans/test/functional/toolchains/meson/test_subproject.py
Original file line number Diff line number Diff line change
@@ -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 <stdio.h>

void hello(void) {
#ifdef FRENCH
printf("Le sous-projet vous salut\\n");
#else
printf("Hello from subproject\\n");
#endif
}
""")

_greeter_c = textwrap.dedent("""
#include <hello.h>

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
25 changes: 25 additions & 0 deletions conans/test/integration/toolchains/meson/test_mesontoolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -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