-
Notifications
You must be signed in to change notification settings - Fork 980
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: MesonToolchain: improve cross-compilation workflow on Apple systems #9711
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,14 +3,15 @@ | |
from jinja2 import Template | ||
|
||
from conan.tools._check_build_profile import check_using_build_profile | ||
from conan.tools.apple.apple import is_apple_os, to_apple_arch, apple_min_version_flag, XCRun | ||
from conan.tools.env import VirtualBuildEnv | ||
from conan.tools.microsoft import VCVars | ||
from conans.client.build.cppstd_flags import cppstd_from_settings | ||
from conan.tools.cross_building import cross_building, get_cross_building_settings | ||
from conans.util.files import save | ||
|
||
|
||
class MesonToolchain(object): | ||
class MesonBaseToolchain(object): | ||
native_filename = "conan_meson_native.ini" | ||
cross_filename = "conan_meson_cross.ini" | ||
|
||
|
@@ -46,7 +47,14 @@ class MesonToolchain(object): | |
c_link_args = {{c_link_args}} | ||
cpp_args = {{cpp_args}} + preprocessor_definitions | ||
cpp_link_args = {{cpp_link_args}} | ||
objc_args = {{objc_args}} + preprocessor_definitions | ||
objc_link_args = {{objc_link_args}} | ||
objcpp_args = {{objcpp_args}} + preprocessor_definitions | ||
objcpp_link_args = {{objcpp_link_args}} | ||
{% if pkg_config_path %}pkg_config_path = {{pkg_config_path}}{% endif %} | ||
|
||
[properties] | ||
{% if root %}root = {{root}}{% endif %} | ||
""") | ||
|
||
_cross_file_template = _native_file_template + textwrap.dedent(""" | ||
|
@@ -111,15 +119,30 @@ def from_build_env(name): | |
|
||
# https://mesonbuild.com/Builtin-options.html#compiler-options | ||
self.cpp_std = self._to_meson_cppstd(self._cppstd) if self._cppstd else None | ||
self.c_args = self._to_meson_value(self._env_array('CPPFLAGS') + self._env_array('CFLAGS')) | ||
self.c_link_args = self._to_meson_value(self._env_array('LDFLAGS')) | ||
self.cpp_args = self._to_meson_value(self._env_array('CPPFLAGS') + | ||
self._env_array('CXXFLAGS')) | ||
self.cpp_link_args = self._to_meson_value(self._env_array('LDFLAGS')) | ||
self.c_args = self._env_array('CPPFLAGS') + self._env_array('CFLAGS') | ||
self.c_link_args = self._env_array('LDFLAGS') | ||
self.cpp_args = self._env_array('CPPFLAGS') + self._env_array('CXXFLAGS') | ||
self.cpp_link_args = self._env_array('LDFLAGS') | ||
self.objc_args = self._env_array('CPPFLAGS') + self._env_array('OBJCFLAGS') | ||
self.objc_link_args = self._env_array('LDFLAGS') | ||
self.objcpp_args = self._env_array('CPPFLAGS') + self._env_array('OBJCXXFLAGS') | ||
self.objcpp_link_args = self._env_array('LDFLAGS') | ||
self.pkg_config_path = "'%s'" % self._conanfile.generators_folder | ||
self.root = None | ||
|
||
check_using_build_profile(self._conanfile) | ||
|
||
super(MesonBaseToolchain, self).__init__(conanfile) | ||
|
||
self.c_args = self._to_meson_value(self.c_args) | ||
self.c_link_args = self._to_meson_value(self.c_link_args) | ||
self.cpp_args = self._to_meson_value(self.cpp_args) | ||
self.cpp_link_args = self._to_meson_value(self.cpp_link_args) | ||
self.objc_args = self._to_meson_value(self.objc_args) | ||
self.objc_link_args = self._to_meson_value(self.objc_link_args) | ||
self.objcpp_args = self._to_meson_value(self.objcpp_args) | ||
self.objcpp_link_args = self._to_meson_value(self.objcpp_link_args) | ||
|
||
def _get_backend(self, recipe_backend): | ||
# Returns the name of the backend used by Meson | ||
conanfile = self._conanfile | ||
|
@@ -238,8 +261,13 @@ def _context(self): | |
"c_link_args": self.c_link_args, | ||
"cpp_args": self.cpp_args, | ||
"cpp_link_args": self.cpp_link_args, | ||
"objc_args": self.objc_args, | ||
"objc_link_args": self.objc_link_args, | ||
"objcpp_args": self.objcpp_args, | ||
"objcpp_link_args": self.objcpp_link_args, | ||
"pkg_config_path": self.pkg_config_path, | ||
"preprocessor_definitions": self.preprocessor_definitions | ||
"preprocessor_definitions": self.preprocessor_definitions, | ||
"root": self.root | ||
} | ||
return context | ||
|
||
|
@@ -333,3 +361,45 @@ def generate(self): | |
else: | ||
self._write_native_file() | ||
VCVars(self._conanfile).generate() | ||
|
||
|
||
class MesonAppleMixin(object): | ||
def __init__(self, conanfile): | ||
if not cross_building(conanfile): | ||
return | ||
if self._base_compiler != 'apple-clang': | ||
return | ||
if not is_apple_os(conanfile.settings.get_safe("os")): | ||
return | ||
|
||
xcrun = XCRun(conanfile.settings) | ||
self.c = self.c or self._to_meson_value(xcrun.cc) | ||
self.cpp = self.cpp or self._to_meson_value(xcrun.cxx) | ||
|
||
arch = to_apple_arch(conanfile.settings.get_safe("arch")) | ||
if arch: | ||
self.extend_args(["-arch", arch]) | ||
|
||
os_version = conanfile.settings.get_safe("os.version") | ||
if os_version: | ||
flag = apple_min_version_flag(conanfile) | ||
self.extend_args([flag]) | ||
|
||
if xcrun.sdk_path: | ||
self.extend_args(["-isysroot", xcrun.sdk_path]) | ||
self.root = self._to_meson_value(xcrun.sdk_path) | ||
|
||
def extend_args(self, args): | ||
self.c_args.extend(args) | ||
self.c_link_args.extend(args) | ||
self.cpp_args.extend(args) | ||
self.cpp_link_args.extend(args) | ||
self.objc_args.extend(args) | ||
self.objc_link_args.extend(args) | ||
self.objcpp_args.extend(args) | ||
self.objcpp_link_args.extend(args) | ||
|
||
|
||
class MesonToolchain(MesonBaseToolchain, MesonAppleMixin): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please, no multiple inheritance. And avoid inheritance as much as possible. Go with composition unless there is an extremely compelling case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's a well-known mixin pattern? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @memsharded do you want me to implement There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, please, avoid inheritance and mixins unless it is strictly necessary. Composition scales better and is more flexible for user customization. Going with a "blocks" approach like the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am looking at how to implement blocks similarly to how it was done in CMake toolchain.
for executables, it should take the very least priority (only use when not explicitly defined), for variables, it should effectively append them to any existing. then, unlike on the other hand, there is an interesting feature that meson supports multiple cross-files overriding each other: https://mesonbuild.com/Machine-files.html#loading-multiple-machine-files so, we may use something like therefore, there are likely 3 major approaches on how to implement that:
and I can't check if it's already defined above, without using an external check in the code.
looking for the advice, thanks! |
||
def __init__(self, conanfile): | ||
super(MesonToolchain, self).__init__(conanfile) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import os | ||
import platform | ||
import sys | ||
import textwrap | ||
import unittest | ||
|
||
import pytest | ||
|
||
from conans.client.tools.apple import XCRun | ||
from conans.test.assets.sources import gen_function_cpp, gen_function_h | ||
from conans.test.utils.tools import TestClient | ||
|
||
|
||
@pytest.mark.tool_meson | ||
@pytest.mark.skipif(sys.version_info.major == 2, reason="Meson not supported in Py2") | ||
@pytest.mark.skipif(platform.system() != "Darwin", reason="requires Xcode") | ||
class M1MesonTestCase(unittest.TestCase): | ||
|
||
_conanfile_py = textwrap.dedent(""" | ||
from conans import ConanFile, tools | ||
from conan.tools.meson import Meson, MesonToolchain | ||
|
||
|
||
class App(ConanFile): | ||
settings = "os", "arch", "compiler", "build_type" | ||
options = {"shared": [True, False], "fPIC": [True, False]} | ||
default_options = {"shared": False, "fPIC": True} | ||
|
||
def config_options(self): | ||
if self.settings.os == "Windows": | ||
del self.options.fPIC | ||
|
||
def generate(self): | ||
tc = MesonToolchain(self) | ||
tc.generate() | ||
|
||
def build(self): | ||
meson = Meson(self) | ||
meson.configure() | ||
meson.build() | ||
""") | ||
|
||
_meson_build = textwrap.dedent(""" | ||
project('tutorial', 'cpp') | ||
add_global_arguments('-DSTRING_DEFINITION="' + get_option('STRING_DEFINITION') + '"', | ||
language : 'cpp') | ||
hello = library('hello', 'hello.cpp') | ||
executable('demo', 'main.cpp', link_with: hello) | ||
""") | ||
|
||
_meson_options_txt = textwrap.dedent(""" | ||
option('STRING_DEFINITION', type : 'string', description : 'a string option') | ||
""") | ||
|
||
def settings(self): | ||
return [("os", "Macos"), | ||
("arch", "armv8"), | ||
("compiler", "apple-clang"), | ||
("compiler.version", "12.0"), | ||
("compiler.libcxx", "libc++")] | ||
|
||
def profile(self): | ||
template = textwrap.dedent(""" | ||
include(default) | ||
[settings] | ||
{settings} | ||
""") | ||
settings = '\n'.join(["%s = %s" % (s[0], s[1]) for s in self.settings()]) | ||
return template.format(settings=settings) | ||
|
||
def test_meson_toolchain(self): | ||
self.xcrun = XCRun(None) | ||
|
||
hello_h = gen_function_h(name="hello") | ||
hello_cpp = gen_function_cpp(name="hello", preprocessor=["STRING_DEFINITION"]) | ||
app = gen_function_cpp(name="main", includes=["hello"], calls=["hello"]) | ||
|
||
self.t = TestClient() | ||
|
||
self.t.save({"conanfile.py": self._conanfile_py, | ||
"meson.build": self._meson_build, | ||
"meson_options.txt": self._meson_options_txt, | ||
"hello.h": hello_h, | ||
"hello.cpp": hello_cpp, | ||
"main.cpp": app, | ||
"profile_host": self.profile()}) | ||
|
||
self.t.run("install . --profile:build=default --profile:host=profile_host") | ||
|
||
self.t.run("build .") | ||
|
||
libhello = os.path.join(self.t.current_folder, "build", "libhello.a") | ||
self.assertTrue(os.path.isfile(libhello)) | ||
demo = os.path.join(self.t.current_folder, "build", "demo") | ||
self.assertTrue(os.path.isfile(demo)) | ||
|
||
lipo = self.xcrun.find('lipo') | ||
|
||
self.t.run_command('"%s" -info "%s"' % (lipo, libhello)) | ||
self.assertIn("architecture: arm64", self.t.out) | ||
|
||
self.t.run_command('"%s" -info "%s"' % (lipo, demo)) | ||
self.assertIn("architecture: arm64", self.t.out) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better move XCRun first to modern approach. It should be less detection based and more configuration based.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the XCRun is already imported from a new namespace
conan.tools.apple
or what exactly do you mean?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
anyway, I think improvements of XCRun, if any, could be done in another, their own PR. this is outside of the scope of this PR IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is that generators like this one do not auto-detect anything. XCRun is deducing things by executing some commands right? That should be moved to input configuration, like the new
[conf]
.This is a general effort we are doing in 2.0, we are removing all the platform, architecture, compiler, env-vars detection in generators and helpers, and that should happen in the input configuration. Please try that approach better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know right now how it should look like and how it should be implemented.
if you have some example, I can take a look, but my point it belongs to XCRun, not to the meson toolchain.
in other words, as I understand, XCRun will have some code to read
conf
file instead of executing commands.I don't see any reason why it should be done inside this particular PR - it is just using XCRun, and for meson toolchain it's an implementation detail how exactly it is implemented under the hood.
I also don't understand how is it going to work in CI, e.g. in case of ConanCenter, e.g. who, when and how exactly should populate conf with the input configutation?
really, I am just relying on the existing implementations of
conan.tools
namespace here, if they are somehow bbad/buggy or don't meet the conan 2.0 requirements, let's create an another issue and improve in another PR.