Repository: conan-io/conan

Issue:
[feature] MesonToolchain: improve cross-compilation workflow on Apple systems
unlike CMake, meson is dumb and doesn't attempt to guess a path to the compiler in case of cross-compiling mode.
it has to be explicitly specified within cross-file

we probably can deduce required things in the conan client, but it's kinda auto-magic...
OTH it makes recipes easier

we may try to populate the contents of the cross-file similar to the examples meson provides:
https://github.com/mesonbuild/meson/blob/master/cross/iphone.txt
https://github.com/mesonbuild/meson/blob/master/cross/tvos.txt

see also:
https://github.com/conan-io/conan-center-index/pull/7360
https://github.com/conan-io/conan-center-index/pull/6678

/cc @ericLemanissier @madebr @SpaceIm

- [ ] I've read the [CONTRIBUTING guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).




Discussion:
I would say that the Toolchain is supposed to do exactly that, gather the settings and write something the build system understands to represent the configuration. 
Said that, I would say we should consider also adjusting the compiler as I commented here: https://github.com/conan-io/conan/issues/9922#issuecomment-970057251
@memsharded 
I'm assigning as look into for the next release and let's see if we can finally do it for this release, wait to the next or...let's see

Resolving patch:
diff --git a/conan/tools/meson/helpers.py b/conan/tools/meson/helpers.py
new file mode 100644
--- /dev/null
+++ b/conan/tools/meson/helpers.py
@@ -0,0 +1,95 @@
+__all__ = ["to_meson_machine", "to_meson_value", "to_cppstd_flag"]
+
+# https://mesonbuild.com/Reference-tables.html#operating-system-names
+_meson_system_map = {
+    'Android': 'android',
+    'Macos': 'darwin',
+    'iOS': 'darwin',
+    'watchOS': 'darwin',
+    'tvOS': 'darwin',
+    'FreeBSD': 'freebsd',
+    'Emscripten': 'emscripten',
+    'Linux': 'linux',
+    'SunOS': 'sunos',
+    'Windows': 'windows',
+    'WindowsCE': 'windows',
+    'WindowsStore': 'windows'
+}
+
+# https://mesonbuild.com/Reference-tables.html#cpu-families
+_meson_cpu_family_map = {
+    'armv4': ('arm', 'armv4', 'little'),
+    'armv4i': ('arm', 'armv4i', 'little'),
+    'armv5el': ('arm', 'armv5el', 'little'),
+    'armv5hf': ('arm', 'armv5hf', 'little'),
+    'armv6': ('arm', 'armv6', 'little'),
+    'armv7': ('arm', 'armv7', 'little'),
+    'armv7hf': ('arm', 'armv7hf', 'little'),
+    'armv7s': ('arm', 'armv7s', 'little'),
+    'armv7k': ('arm', 'armv7k', 'little'),
+    'armv8': ('aarch64', 'armv8', 'little'),
+    'armv8_32': ('aarch64', 'armv8_32', 'little'),
+    'armv8.3': ('aarch64', 'armv8.3', 'little'),
+    'avr': ('avr', 'avr', 'little'),
+    'mips': ('mips', 'mips', 'big'),
+    'mips64': ('mips64', 'mips64', 'big'),
+    'ppc32be': ('ppc', 'ppc', 'big'),
+    'ppc32': ('ppc', 'ppc', 'little'),
+    'ppc64le': ('ppc64', 'ppc64', 'little'),
+    'ppc64': ('ppc64', 'ppc64', 'big'),
+    's390': ('s390', 's390', 'big'),
+    's390x': ('s390x', 's390x', 'big'),
+    'sh4le': ('sh4', 'sh4', 'little'),
+    'sparc': ('sparc', 'sparc', 'big'),
+    'sparcv9': ('sparc64', 'sparc64', 'big'),
+    'wasm': ('wasm32', 'wasm32', 'little'),
+    'x86': ('x86', 'x86', 'little'),
+    'x86_64': ('x86_64', 'x86_64', 'little')
+}
+
+_vs_cppstd_map = {
+    '14': "vc++14",
+    '17': "vc++17",
+    '20': "vc++latest"
+}
+
+_cppstd_map = {
+    '98': "c++03", 'gnu98': "gnu++03",
+    '11': "c++11", 'gnu11': "gnu++11",
+    '14': "c++14", 'gnu14': "gnu++14",
+    '17': "c++17", 'gnu17': "gnu++17",
+    '20': "c++1z", 'gnu20': "gnu++1z"
+}
+
+
+def to_meson_machine(machine_os, machine_arch):
+    system = _meson_system_map.get(machine_os, machine_os.lower())
+    default_cpu_tuple = (machine_arch.lower(), machine_arch.lower(), 'little')
+    cpu_tuple = _meson_cpu_family_map.get(machine_arch, default_cpu_tuple)
+    cpu_family, cpu, endian = cpu_tuple[0], cpu_tuple[1], cpu_tuple[2]
+    context = {
+        'system': system,
+        'cpu_family': cpu_family,
+        'cpu': cpu,
+        'endian': endian,
+    }
+    return context
+
+
+def to_meson_value(value):
+    # https://mesonbuild.com/Machine-files.html#data-types
+    if isinstance(value, str):
+        return "'%s'" % value
+    elif isinstance(value, bool):
+        return 'true' if value else "false"
+    elif isinstance(value, list):
+        return '[%s]' % ', '.join([str(to_meson_value(val)) for val in value])
+    return value
+
+
+# FIXME: Move to another more common module
+def to_cppstd_flag(compiler, cppstd):
+    if compiler in ("msvc", "Visual Studio"):
+        return _vs_cppstd_map.get(cppstd)
+    else:
+        return _cppstd_map.get(cppstd)
diff --git a/conan/tools/meson/meson.py b/conan/tools/meson/meson.py
--- a/conan/tools/meson/meson.py
+++ b/conan/tools/meson/meson.py
@@ -1,40 +1,33 @@
 import os
 
 from conan.tools.build import build_jobs
-from conan.tools.cross_building import cross_building
 from conan.tools.meson import MesonToolchain
 
 
 class Meson(object):
-    def __init__(self, conanfile, build_folder='build'):
+    def __init__(self, conanfile):
         self._conanfile = conanfile
-        self._build_folder = build_folder
-
-    @property
-    def _build_dir(self):
-        build = self._conanfile.build_folder
-        if self._build_folder:
-            build = os.path.join(self._conanfile.build_folder, self._build_folder)
-        return build
-
-    def configure(self, source_folder=None):
-        source = self._conanfile.source_folder
-        if source_folder:
-            source = os.path.join(self._conanfile.source_folder, source_folder)
 
+    def configure(self):
+        source_folder = self._conanfile.source_folder
+        build_folder = self._conanfile.build_folder
         cmd = "meson setup"
-        if cross_building(self._conanfile):
-            cmd += ' --cross-file "{}"'.format(MesonToolchain.cross_filename)
+        generators_folder = self._conanfile.generators_folder
+        cross = os.path.join(generators_folder, MesonToolchain.cross_filename)
+        native = os.path.join(generators_folder, MesonToolchain.native_filename)
+        if os.path.exists(cross):
+            cmd += ' --cross-file "{}"'.format(cross)
         else:
-            cmd += ' --native-file "{}"'.format(MesonToolchain.native_filename)
-        cmd += ' "{}" "{}"'.format(self._build_dir, source)
+            cmd += ' --native-file "{}"'.format(native)
+        cmd += ' "{}" "{}"'.format(build_folder, source_folder)
         if self._conanfile.package_folder:
             cmd += ' -Dprefix="{}"'.format(self._conanfile.package_folder)
         self._conanfile.output.info("Meson configure cmd: {}".format(cmd))
         self._conanfile.run(cmd)
 
     def build(self, target=None):
-        cmd = 'meson compile -C "{}"'.format(self._build_dir)
+        meson_build_folder = self._conanfile.build_folder
+        cmd = 'meson compile -C "{}"'.format(meson_build_folder)
         njobs = build_jobs(self._conanfile)
         if njobs:
             cmd += " -j{}".format(njobs)
@@ -44,13 +37,13 @@ def build(self, target=None):
         self._conanfile.run(cmd)
 
     def install(self):
-        cmd = 'meson install -C "{}"'.format(self._build_dir)
-        # TODO: Do we need vcvars for install?
-        vcvars = os.path.join(self._conanfile.install_folder, "conanvcvars")
+        meson_build_folder = self._conanfile.build_folder
+        cmd = 'meson install -C "{}"'.format(meson_build_folder)
         self._conanfile.run(cmd)
 
     def test(self):
-        cmd = 'meson test -v -C "{}"'.format(self._build_dir)
+        meson_build_folder = self._conanfile.build_folder
+        cmd = 'meson test -v -C "{}"'.format(meson_build_folder)
         # TODO: Do we need vcvars for test?
         # TODO: This should use conanrunenv, but what if meson itself is a build-require?
         self._conanfile.run(cmd)
diff --git a/conan/tools/meson/toolchain.py b/conan/tools/meson/toolchain.py
--- a/conan/tools/meson/toolchain.py
+++ b/conan/tools/meson/toolchain.py
@@ -3,10 +3,12 @@
 from jinja2 import Template
 
 from conan.tools._check_build_profile import check_using_build_profile
-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.apple.apple import to_apple_arch, is_apple_os, apple_min_version_flag
 from conan.tools.cross_building import cross_building, get_cross_building_settings
+from conan.tools.env import VirtualBuildEnv
+from conan.tools.meson.helpers import *
+from conan.tools.microsoft import VCVars, msvc_runtime_flag
+from conans.errors import ConanException
 from conans.util.files import save
 
 
@@ -14,203 +16,183 @@ class MesonToolchain(object):
     native_filename = "conan_meson_native.ini"
     cross_filename = "conan_meson_cross.ini"
 
-    _native_file_template = textwrap.dedent("""
+    _meson_file_template = textwrap.dedent("""
     [constants]
     preprocessor_definitions = [{% for it, value in preprocessor_definitions.items() -%}
     '-D{{ it }}="{{ value}}"'{%- if not loop.last %}, {% endif %}{% endfor %}]
 
     [project options]
-    {{project_options}}
+    {% for it, value in project_options.items() -%}
+    {{it}} = {{value}}
+    {% endfor %}
 
     [binaries]
-    {% if c %}c = {{c}}{% endif %}
-    {% if cpp %}cpp = {{cpp}}{% endif %}
-    {% if c_ld %}c_ld = {{c_ld}}{% endif %}
-    {% if cpp_ld %}cpp_ld = {{cpp_ld}}{% endif %}
-    {% if ar %}ar = {{ar}}{% endif %}
-    {% if strip %}strip = {{strip}}{% endif %}
-    {% if as %}as = {{as}}{% endif %}
-    {% if windres %}windres = {{windres}}{% endif %}
-    {% if pkgconfig %}pkgconfig = {{pkgconfig}}{% endif %}
+    {% if c %}c = '{{c}}'{% endif %}
+    {% if cpp %}cpp = '{{cpp}}'{% endif %}
+    {% if c_ld %}c_ld = '{{c_ld}}'{% endif %}
+    {% if cpp_ld %}cpp_ld = '{{cpp_ld}}'{% endif %}
+    {% if ar %}ar = '{{ar}}'{% endif %}
+    {% if strip %}strip = '{{strip}}'{% endif %}
+    {% if as %}as = '{{as}}'{% endif %}
+    {% if windres %}windres = '{{windres}}'{% endif %}
+    {% if pkgconfig %}pkgconfig = '{{pkgconfig}}'{% endif %}
 
     [built-in options]
     {% if buildtype %}buildtype = {{buildtype}}{% endif %}
     {% if debug %}debug = {{debug}}{% endif %}
     {% if default_library %}default_library = {{default_library}}{% endif %}
-    {% if b_vscrt %}b_vscrt = {{b_vscrt}}{% endif %}
+    {% if b_vscrt %}b_vscrt = '{{b_vscrt}}' {% endif %}
     {% if b_ndebug %}b_ndebug = {{b_ndebug}}{% endif %}
     {% if b_staticpic %}b_staticpic = {{b_staticpic}}{% endif %}
-    {% if cpp_std %}cpp_std = {{cpp_std}}{% endif %}
-    {% if backend %}backend = {{backend}}{% endif %}
+    {% if cpp_std %}cpp_std = '{{cpp_std}}' {% endif %}
+    {% if backend %}backend = '{{backend}}' {% endif %}
     c_args = {{c_args}} + preprocessor_definitions
     c_link_args = {{c_link_args}}
     cpp_args = {{cpp_args}} + preprocessor_definitions
     cpp_link_args = {{cpp_link_args}}
-    {% if pkg_config_path %}pkg_config_path = {{pkg_config_path}}{% endif %}
-    """)
-
-    _cross_file_template = _native_file_template + textwrap.dedent("""
-    [build_machine]
-    {{build_machine}}
-
-    [host_machine]
-    {{host_machine}}
-
-    [target_machine]
-    {{target_machine}}
-    """)
-
-    _machine_template = textwrap.dedent("""
-    system = {{system}}
-    cpu_family = {{cpu_family}}
-    cpu = {{cpu}}
-    endian = {{endian}}
+    {% if pkg_config_path %}pkg_config_path = '{{pkg_config_path}}'{% endif %}
+
+    {% for context, values in cross_build.items() %}
+    [{{context}}_machine]
+    system = '{{values["system"]}}'
+    cpu_family = '{{values["cpu_family"]}}'
+    cpu = '{{values["cpu"]}}'
+    endian = '{{values["endian"]}}'
+    {% endfor %}
     """)
 
     def __init__(self, conanfile, backend=None):
         self._conanfile = conanfile
-        self._backend = self._get_backend(backend)
-        self._build_type = self._conanfile.settings.get_safe("build_type")
-        self._base_compiler = self._conanfile.settings.get_safe("compiler.base") or \
-                              self._conanfile.settings.get_safe("compiler")
-        self._vscrt = self._conanfile.settings.get_safe("compiler.base.runtime") or \
-                      self._conanfile.settings.get_safe("compiler.runtime")
-        self._cppstd = cppstd_from_settings(self._conanfile.settings)
-        self._shared = self._conanfile.options.get_safe("shared")
-        self._fpic = self._conanfile.options.get_safe("fPIC")
-        self._build_env = VirtualBuildEnv(self._conanfile).vars()
-
-        self.definitions = dict()
-        self.preprocessor_definitions = dict()
-
-        def from_build_env(name):
-            return self._to_meson_value(self._build_env.get(name, None))
+        # Values are kept as Python built-ins so users can modify them more easily, and they are
+        # only converted to Meson file syntax for rendering
+        # priority: first user conf, then recipe, last one is default "ninja"
+        backend_conf = conanfile.conf["tools.meson.mesontoolchain:backend"]
+        self._backend = backend_conf or backend or 'ninja'
 
-        self.c = from_build_env("CC")
-        self.cpp = from_build_env("CXX")
-        self.c_ld = from_build_env("CC_LD") or from_build_env("LD")
-        self.cpp_ld = from_build_env("CXX_LD") or from_build_env("LD")
-        self.ar = from_build_env("AR")
-        self.strip = from_build_env("STRIP")
-        self.as_ = from_build_env("AS")
-        self.windres = from_build_env("WINDRES")
-        self.pkgconfig = from_build_env("PKG_CONFIG")
+        build_type = self._conanfile.settings.get_safe("build_type")
+        self._buildtype = {"Debug": "debug",  # Note, it is not "'debug'"
+                           "Release": "release",
+                           "MinSizeRel": "minsize",
+                           "RelWithDebInfo": "debugoptimized"}.get(build_type, build_type)
+        self._b_ndebug = "true" if self._buildtype != "debug" else "false"
 
+        # https://mesonbuild.com/Builtin-options.html#base-options
+        fpic = self._conanfile.options.get_safe("fPIC")
+        shared = self._conanfile.options.get_safe("shared")
+        self._b_staticpic = fpic if (shared is False and fpic is not None) else None
         # https://mesonbuild.com/Builtin-options.html#core-options
         # Do not adjust "debug" if already adjusted "buildtype"
-        self.buildtype = self._to_meson_build_type(self._build_type) if self._build_type else None
-        self.default_library = self._to_meson_shared(self._shared) \
-            if self._shared is not None else None
-        self.backend = self._to_meson_value(self._backend)
-
-        # https://mesonbuild.com/Builtin-options.html#base-options
-        self.b_vscrt = self._to_meson_vscrt(self._vscrt)
-        self.b_staticpic = self._to_meson_value(self._fpic) \
-            if (self._shared is False and self._fpic is not None) else None
-        self.b_ndebug = self._to_meson_value(self._ndebug) if self._build_type else None
+        self._default_library = ("shared" if shared else "static") if shared is not None else None
+
+        compiler = self._conanfile.settings.get_safe("compiler")
+        cppstd = self._conanfile.settings.get_safe("compiler.cppstd")
+        self._cpp_std = to_cppstd_flag(compiler, cppstd)
+
+        if compiler == "Visual Studio":
+            vscrt = self._conanfile.settings.get_safe("compiler.runtime")
+            self._b_vscrt = str(vscrt).lower()
+        elif compiler == "msvc":
+            vscrt = msvc_runtime_flag(self._conanfile)
+            self._b_vscrt = str(vscrt).lower()
+        else:
+            self._b_vscrt = None
 
-        # 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.pkg_config_path = "'%s'" % self._conanfile.generators_folder
+        self.project_options = {}
+        self.preprocessor_definitions = {}
+        self.pkg_config_path = self._conanfile.generators_folder
 
         check_using_build_profile(self._conanfile)
 
-    def _get_backend(self, recipe_backend):
-        # Returns the name of the backend used by Meson
+        self.cross_build = {}
+        default_comp = ""
+        default_comp_cpp = ""
+        if cross_building(conanfile, skip_x64_x86=True):
+            os_build, arch_build, os_host, arch_host = get_cross_building_settings(self._conanfile)
+            self.cross_build["build"] = to_meson_machine(os_build, arch_build)
+            self.cross_build["host"] = to_meson_machine(os_host, arch_host)
+            if hasattr(conanfile, 'settings_target') and conanfile.settings_target:
+                settings_target = conanfile.settings_target
+                os_target = settings_target.get_safe("os")
+                arch_target = settings_target.get_safe("arch")
+                self.cross_build["target"] = to_meson_machine(os_target, arch_target)
+            if is_apple_os(os_host):  # default cross-compiler in Apple is common
+                default_comp = "clang"
+                default_comp_cpp = "clang++"
+        else:
+            if "Visual" in compiler or compiler == "msvc":
+                default_comp = "cl"
+                default_comp_cpp = "cl"
+            elif "clang" in compiler:
+                default_comp = "clang"
+                default_comp_cpp = "clang++"
+            elif compiler == "gcc":
+                default_comp = "gcc"
+                default_comp_cpp = "g++"
+
+        # Read the VirtualBuildEnv to update the variables
+        build_env = VirtualBuildEnv(self._conanfile).vars()
+        self.c = build_env.get("CC") or default_comp
+        self.cpp = build_env.get("CXX") or default_comp_cpp
+        self.c_ld = build_env.get("CC_LD") or build_env.get("LD")
+        self.cpp_ld = build_env.get("CXX_LD") or build_env.get("LD")
+        self.ar = build_env.get("AR")
+        self.strip = build_env.get("STRIP")
+        self.as_ = build_env.get("AS")
+        self.windres = build_env.get("WINDRES")
+        self.pkgconfig = build_env.get("PKG_CONFIG")
+        self.c_args = build_env.get("CFLAGS", "")
+        self.c_link_args = build_env.get("LDFLAGS", "")
+        self.cpp_args = build_env.get("CXXFLAGS", "")
+        self.cpp_link_args = build_env.get("LDFLAGS", "")
+
+        self._add_apple_flags()
+
+    def _add_apple_flags(self):
         conanfile = self._conanfile
-        # Downstream consumer always higher priority
-        backend_conf = conanfile.conf["tools.meson.mesontoolchain:backend"]
-        if backend_conf:
-            return backend_conf
-        # second priority: the recipe one:
-        if recipe_backend:
-            return recipe_backend
-        # if not defined, deduce automatically the default one (ninja)
-        return 'ninja'
-
-    @staticmethod
-    def _to_meson_value(value):
-        # https://mesonbuild.com/Machine-files.html#data-types
-        import six
-
-        try:
-            from collections.abc import Iterable
-        except ImportError:
-            from collections import Iterable
-
-        if isinstance(value, six.string_types):
-            return "'%s'" % value
-        elif isinstance(value, bool):
-            return 'true' if value else "false"
-        elif isinstance(value, six.integer_types):
-            return value
-        elif isinstance(value, Iterable):
-            return '[%s]' % ', '.join([str(MesonToolchain._to_meson_value(v)) for v in value])
-        return value
-
-    @staticmethod
-    def _to_meson_build_type(build_type):
-        return {"Debug": "'debug'",
-                "Release": "'release'",
-                "MinSizeRel": "'minsize'",
-                "RelWithDebInfo": "'debugoptimized'"}.get(build_type, "'%s'" % build_type)
-    # FIXME : use 'custom' otherwise? or use just None?
+        os_ = conanfile.settings.get_safe("os")
+        if not is_apple_os(os_):
+            return
+
+        # SDK path is mandatory for cross-building
+        sdk_path = conanfile.conf["tools.meson.mesontoolchain:sdk_path"]
+        if not sdk_path and self.cross_build:
+            raise ConanException("You must provide a valid SDK path for cross-compilation.")
+
+        # TODO: Delete this os_sdk check whenever the _guess_apple_sdk_name() function disappears
+        os_sdk = conanfile.settings.get_safe('os.sdk')
+        if not os_sdk and os_ != "Macos":
+            raise ConanException("Please, specify a suitable value for os.sdk.")
+
+        arch = to_apple_arch(conanfile.settings.get_safe("arch"))
+        # Calculating the main Apple flags
+        deployment_target_flag = apple_min_version_flag(conanfile)
+        sysroot_flag = "-isysroot " + sdk_path if sdk_path else ""
+        arch_flag = "-arch " + arch if arch else ""
+
+        apple_flags = {}
+        if deployment_target_flag:
+            flag_ = deployment_target_flag.split("=")[0]
+            apple_flags[flag_] = deployment_target_flag
+        if sysroot_flag:
+            apple_flags["-isysroot"] = sysroot_flag
+        if arch_flag:
+            apple_flags["-arch"] = arch_flag
+
+        for flag, arg_value in apple_flags.items():
+            v = " " + arg_value
+            if flag not in self.c_args:
+                self.c_args += v
+            if flag not in self.c_link_args:
+                self.c_link_args += v
+            if flag not in self.cpp_args:
+                self.cpp_args += v
+            if flag not in self.cpp_link_args:
+                self.cpp_link_args += v
 
-    @property
-    def _debug(self):
-        return self._build_type == "Debug"
-
-    @property
-    def _ndebug(self):
-        # ERROR: Value "True" (of type "boolean") for combo option "Disable asserts" is not one of
-        # the choices. Possible choices are (as string): "true", "false", "if-release".
-        return "true" if self._build_type != "Debug" else "false"
-
-    @staticmethod
-    def _to_meson_vscrt(vscrt):
-        return {"MD": "'md'",
-                "MDd": "'mdd'",
-                "MT": "'mt'",
-                "MTd": "'mtd'"}.get(vscrt, "'none'")
-
-    @staticmethod
-    def _to_meson_shared(shared):
-        return "'shared'" if shared else "'static'"
-
-    def _to_meson_cppstd(self, cppstd):
-        if self._base_compiler == "Visual Studio":
-            return {'14': "'vc++14'",
-                    '17': "'vc++17'",
-                    '20': "'vc++latest'"}.get(cppstd, "'none'")
-        return {'98': "'c++03'", 'gnu98': "'gnu++03'",
-                '11': "'c++11'", 'gnu11': "'gnu++11'",
-                '14': "'c++14'", 'gnu14': "'gnu++14'",
-                '17': "'c++17'", 'gnu17': "'gnu++17'",
-                '20': "'c++1z'", 'gnu20': "'gnu++1z'"}.get(cppstd, "'none'")
-
-    @staticmethod
-    def _none_if_empty(value):
-        return "'%s'" % value if value.strip() else None
-
-    def _env_array(self, name):
-        import shlex
-        return shlex.split(self._build_env.get(name, ''))
-
-    @property
     def _context(self):
-        project_options = []
-        for k, v in self.definitions.items():
-            project_options.append("%s = %s" % (k, self._to_meson_value(v)))
-        project_options = "\n".join(project_options)
-
-        context = {
+        return {
             # https://mesonbuild.com/Machine-files.html#project-specific-options
-            "project_options": project_options,
+            "project_options": {k: to_meson_value(v) for k, v in self.project_options.items()},
             # https://mesonbuild.com/Builtin-options.html#directories
             # TODO : we don't manage paths like libdir here (yet?)
             # https://mesonbuild.com/Machine-files.html#binaries
@@ -225,111 +207,32 @@ def _context(self):
             "windres": self.windres,
             "pkgconfig": self.pkgconfig,
             # https://mesonbuild.com/Builtin-options.html#core-options
-            "buildtype": self.buildtype,
-            "default_library": self.default_library,
-            "backend": self.backend,
+            "buildtype": to_meson_value(self._buildtype),
+            "default_library": to_meson_value(self._default_library),
+            "backend": self._backend,
             # https://mesonbuild.com/Builtin-options.html#base-options
-            "b_vscrt": self.b_vscrt,
-            "b_staticpic": self.b_staticpic,
-            "b_ndebug": self.b_ndebug,
+            "b_vscrt": self._b_vscrt,
+            "b_staticpic": to_meson_value(self._b_staticpic),
+            "b_ndebug": to_meson_value(self._b_ndebug),
             # https://mesonbuild.com/Builtin-options.html#compiler-options
-            "cpp_std": self.cpp_std,
-            "c_args": self.c_args,
-            "c_link_args": self.c_link_args,
-            "cpp_args": self.cpp_args,
-            "cpp_link_args": self.cpp_link_args,
+            "cpp_std": to_meson_value(self._cpp_std),
+            "c_args": to_meson_value(self.c_args.strip().split()),
+            "c_link_args": to_meson_value(self.c_link_args.strip().split()),
+            "cpp_args": to_meson_value(self.cpp_args.strip().split()),
+            "cpp_link_args": to_meson_value(self.cpp_link_args.strip().split()),
             "pkg_config_path": self.pkg_config_path,
-            "preprocessor_definitions": self.preprocessor_definitions
+            "preprocessor_definitions": self.preprocessor_definitions,
+            "cross_build": self.cross_build
         }
-        return context
-
-    @staticmethod
-    def _render(template, context):
-        t = Template(template)
-        return t.render(context)
-
-    @property
-    def _native_content(self):
-        return self._render(self._native_file_template, self._context)
-
-    def _to_meson_machine(self, machine_os, machine_arch):
-        # https://mesonbuild.com/Reference-tables.html#operating-system-names
-        system_map = {'Android': 'android',
-                      'Macos': 'darwin',
-                      'iOS': 'darwin',
-                      'watchOS': 'darwin',
-                      'tvOS': 'darwin',
-                      'FreeBSD': 'freebsd',
-                      'Emscripten': 'emscripten',
-                      'Linux': 'linux',
-                      'SunOS': 'sunos',
-                      'Windows': 'windows',
-                      'WindowsCE': 'windows',
-                      'WindowsStore': 'windows'}
-        # https://mesonbuild.com/Reference-tables.html#cpu-families
-        cpu_family_map = {'armv4': ('arm', 'armv4', 'little'),
-                          'armv4i': ('arm', 'armv4i', 'little'),
-                          'armv5el': ('arm', 'armv5el', 'little'),
-                          'armv5hf': ('arm', 'armv5hf', 'little'),
-                          'armv6': ('arm', 'armv6', 'little'),
-                          'armv7': ('arm', 'armv7', 'little'),
-                          'armv7hf': ('arm', 'armv7hf', 'little'),
-                          'armv7s': ('arm', 'armv7s', 'little'),
-                          'armv7k':('arm', 'armv7k', 'little'),
-                          'armv8': ('aarch64', 'armv8', 'little'),
-                          'armv8_32': ('aarch64', 'armv8_32', 'little'),
-                          'armv8.3': ('aarch64', 'armv8.3', 'little'),
-                          'avr': ('avr', 'avr', 'little'),
-                          'mips': ('mips', 'mips', 'big'),
-                          'mips64': ('mips64', 'mips64', 'big'),
-                          'ppc32be': ('ppc', 'ppc', 'big'),
-                          'ppc32': ('ppc', 'ppc', 'little'),
-                          'ppc64le': ('ppc64', 'ppc64', 'little'),
-                          'ppc64': ('ppc64', 'ppc64', 'big'),
-                          's390': ('s390', 's390', 'big'),
-                          's390x': ('s390x', 's390x', 'big'),
-                          'sh4le': ('sh4', 'sh4', 'little'),
-                          'sparc': ('sparc', 'sparc', 'big'),
-                          'sparcv9': ('sparc64', 'sparc64', 'big'),
-                          'wasm': ('wasm32', 'wasm32', 'little'),
-                          'x86': ('x86', 'x86', 'little'),
-                          'x86_64': ('x86_64', 'x86_64', 'little')}
-        system = system_map.get(machine_os, machine_os.lower())
-        default_cpu_tuple = (machine_arch.lower(), machine_arch.lower(), 'little')
-        cpu_tuple = cpu_family_map.get(machine_arch, default_cpu_tuple)
-        cpu_family, cpu, endian = cpu_tuple[0], cpu_tuple[1], cpu_tuple[2]
-        context = {
-            'system': self._to_meson_value(system),
-            'cpu_family': self._to_meson_value(cpu_family),
-            'cpu': self._to_meson_value(cpu),
-            'endian': self._to_meson_value(endian),
-        }
-        return self._render(self._machine_template, context)
 
     @property
-    def _cross_content(self):
-        os_build, arch_build, os_host, arch_host = get_cross_building_settings(self._conanfile)
-        os_target, arch_target = os_host, arch_host  # TODO: assume target the same as a host for now?
-
-        build_machine = self._to_meson_machine(os_build, arch_build)
-        host_machine = self._to_meson_machine(os_host, arch_host)
-        target_machine = self._to_meson_machine(os_target, arch_target)
-
-        context = self._context
-        context['build_machine'] = build_machine
-        context['host_machine'] = host_machine
-        context['target_machine'] = target_machine
-        return self._render(self._cross_file_template, context)
-
-    def _write_native_file(self):
-        save(self.native_filename, self._native_content)
-
-    def _write_cross_file(self):
-        save(self.cross_filename, self._cross_content)
+    def content(self):
+        context = self._context()
+        content = Template(self._meson_file_template).render(context)
+        return content
 
     def generate(self):
-        if cross_building(self._conanfile):
-            self._write_cross_file()
-        else:
-            self._write_native_file()
+        filename = self.native_filename if not self.cross_build else self.cross_filename
+        save(filename, self.content)
+        # FIXME: Should we check the OS and compiler to call VCVars?
         VCVars(self._conanfile).generate()


Test patch:
diff --git a/conans/test/functional/toolchains/meson/test_android.py b/conans/test/functional/toolchains/meson/test_android.py
deleted file mode 100644
--- a/conans/test/functional/toolchains/meson/test_android.py
+++ /dev/null
@@ -1,164 +0,0 @@
-import os
-import sys
-
-import platform
-import textwrap
-import unittest
-
-from parameterized import parameterized
-import pytest
-
-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")
-class AndroidToolchainMesonTestCase(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 setUp(self):
-        if not self._ndk:
-            raise unittest.SkipTest("requires Android NDK")
-
-    @property
-    def _ndk(self):
-        return os.getenv("ANDROID_NDK")
-
-    @property
-    def _ndk_bin(self):
-        host = "%s-x86_64" % platform.system().lower()
-        return os.path.join(self._ndk, "toolchains", "llvm", "prebuilt", host, "bin")
-
-    @property
-    def _api_level(self):
-        return "21"
-
-    def settings(self):
-        return [("os", "Android"),
-                ("os.api_level", self._api_level),
-                ("arch", self.arch),
-                ("compiler", "clang"),
-                ("compiler.version", "9"),
-                ("compiler.libcxx", "c++_shared")]
-
-    @property
-    def _target(self):
-        return {'armv7': 'armv7a-linux-androideabi%s',
-                'armv8': 'aarch64-linux-android%s',
-                'x86': 'i686-linux-android%s',
-                'x86_64': 'x86_64-linux-android%s'}.get(self.arch) % self._api_level
-
-    @property
-    def _prefix(self):
-        return {'armv7': 'arm-linux-androideabi',
-                'armv8': 'aarch64-linux-android',
-                'x86': 'i686-linux-android',
-                'x86_64': 'x86_64-linux-android'}.get(self.arch)
-
-    def _tool(self, name):
-        return os.path.join(self._ndk_bin, "%s-%s" % (self._prefix, name))
-
-    def env(self):
-        cc = os.path.join(self._ndk_bin, "clang")
-        cxx = os.path.join(self._ndk_bin, "clang++")
-        ar = self._tool('ar')
-        cflags = '--target=%s' % self._target
-        cxxflags = '--target=%s' % self._target
-        ldflags = '--target=%s' % self._target
-
-        return {'CC': cc,
-                'CXX': cxx,
-                'AR': ar,
-                'CFLAGS': cflags,
-                'CXXFLAGS': cxxflags,
-                'LDFLAGS': ldflags}
-
-    def profile(self):
-        template = textwrap.dedent("""
-            include(default)
-            [settings]
-            {settings}
-            [env]
-            {env}
-            """)
-        settings = '\n'.join(["%s = %s" % (s[0], s[1]) for s in self.settings()])
-        env = '\n'.join(["%s = %s" % (k, v) for k, v in self.env().items()])
-        return template.format(settings=settings, env=env)
-
-    @parameterized.expand([('armv8', 'aarch64', 'ELF64', 'AArch64'),
-                           ('armv7', 'arm', 'ELF32', 'ARM'),
-                           ('x86', 'i386', 'ELF32', 'Intel 80386'),
-                           ('x86_64', 'i386:x86-64', 'ELF64', 'Advanced Micro Devices X86-64')
-                           ])
-    def test_meson_toolchain(self, arch, expected_arch, expected_class, expected_machine):
-        self.arch = arch
-
-        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))
-
-        readelf = self._tool('readelf')
-        objdump = self._tool('objdump')
-
-        self.t.run_command('"%s" -f "%s"' % (objdump, libhello))
-        self.assertIn("architecture: %s" % expected_arch, self.t.out)
-
-        self.t.run_command('"%s" -h "%s"' % (readelf, demo))
-        self.assertIn("Class:                             %s" % expected_class, self.t.out)
-        self.assertIn("OS/ABI:                            UNIX - System V", self.t.out)
-        self.assertIn("Machine:                           %s" % expected_machine, self.t.out)
diff --git a/conans/test/integration/toolchains/meson/test_backend.py b/conans/test/functional/toolchains/meson/test_backend.py
similarity index 96%
rename from conans/test/integration/toolchains/meson/test_backend.py
rename to conans/test/functional/toolchains/meson/test_backend.py
--- a/conans/test/integration/toolchains/meson/test_backend.py
+++ b/conans/test/functional/toolchains/meson/test_backend.py
@@ -27,6 +27,9 @@ def config_options(self):
                 if self.settings.os == "Windows":
                     del self.options.fPIC
 
+            def layout(self):
+                self.folders.build = "build"
+
             def generate(self):
                 tc = MesonToolchain(self, backend='vs')
                 tc.generate()
diff --git a/conans/test/functional/toolchains/meson/test_cross_compilation.py b/conans/test/functional/toolchains/meson/test_cross_compilation.py
new file mode 100644
--- /dev/null
+++ b/conans/test/functional/toolchains/meson/test_cross_compilation.py
@@ -0,0 +1,266 @@
+import os
+import platform
+import sys
+import textwrap
+import unittest
+
+import pytest
+from parameterized import parameterized
+
+from conans.client.tools.apple import XCRun, to_apple_arch
+from conans.test.assets.sources import gen_function_cpp, gen_function_h
+from conans.test.utils.tools import TestClient
+
+_conanfile_py = textwrap.dedent("""
+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]}
+    default_options = {"shared": False, "fPIC": True}
+
+    def layout(self):
+        self.folders.build = "build"
+
+    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')
+""")
+
+
+@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")
+@pytest.mark.parametrize("arch, os_, os_version, sdk", [
+    ('armv8', 'iOS', '10.0', 'iphoneos'),
+    ('armv7', 'iOS', '10.0', 'iphoneos'),
+    ('x86', 'iOS', '10.0', 'iphonesimulator'),
+    ('x86_64', 'iOS', '10.0', 'iphonesimulator'),
+    ('armv8', 'Macos', None, None)  # MacOS M1
+])
+def test_apple_meson_toolchain_cross_compiling(arch, os_, os_version, sdk):
+    profile = textwrap.dedent("""
+    include(default)
+
+    [settings]
+    os = {os}
+    os.version = {os_version}
+    os.sdk = {os_sdk}
+    arch = {arch}
+    compiler = apple-clang
+    compiler.version = 12.0
+    compiler.libcxx = libc++
+
+    [conf]
+    tools.meson.mesontoolchain:sdk_path={sdk_path}
+    """)
+
+    xcrun = XCRun(None, sdk)
+    sdk_path = xcrun.sdk_path
+
+    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"])
+    profile = profile.format(
+        os=os_,
+        os_version=os_version,
+        os_sdk=sdk,
+        arch=arch,
+        sdk_path=sdk_path)
+
+    t = TestClient()
+    t.save({"conanfile.py": _conanfile_py,
+            "meson.build": _meson_build,
+            "meson_options.txt": _meson_options_txt,
+            "hello.h": hello_h,
+            "hello.cpp": hello_cpp,
+            "main.cpp": app,
+            "profile_host": profile})
+
+    t.run("install . --profile:build=default --profile:host=profile_host")
+    t.run("build .")
+
+    libhello = os.path.join(t.current_folder, "build", "libhello.a")
+    assert os.path.isfile(libhello) is True
+    demo = os.path.join(t.current_folder, "build", "demo")
+    assert os.path.isfile(demo) is True
+
+    lipo = xcrun.find('lipo')
+
+    t.run_command('"%s" -info "%s"' % (lipo, libhello))
+    assert "architecture: %s" % to_apple_arch(arch) in t.out
+
+    t.run_command('"%s" -info "%s"' % (lipo, demo))
+    assert "architecture: %s" % to_apple_arch(arch) in t.out
+
+
+@pytest.mark.tool_meson
+@pytest.mark.skipif(sys.version_info.major == 2, reason="Meson not supported in Py2")
+# for Linux, build for x86 will require a multilib compiler
+# for macOS, build for x86 is no longer supported by modern Xcode
+@pytest.mark.skipif(platform.system() != "Windows", reason="requires Windows")
+def test_windows_cross_compiling_x86():
+    meson_build = textwrap.dedent("""
+        project('tutorial', 'cpp')
+        executable('demo', 'main.cpp')
+        """)
+    main_cpp = gen_function_cpp(name="main")
+    profile_x86 = textwrap.dedent("""
+        include(default)
+        [settings]
+        arch=x86
+        """)
+
+    client = TestClient()
+    client.save({"conanfile.py": _conanfile_py,
+                 "meson.build": meson_build,
+                 "main.cpp": main_cpp,
+                 "x86": profile_x86})
+    profile_str = "--profile:build=default --profile:host=x86"
+    client.run("install . %s" % profile_str)
+    client.run("build .")
+    client.run_command(os.path.join("build", "demo"))
+
+    assert "main _M_IX86 defined" in client.out
+    assert "main _MSC_VER19" in client.out
+    assert "main _MSVC_LANG2014" in client.out
+
+
+@pytest.mark.tool_meson
+@pytest.mark.skipif(sys.version_info.major == 2, reason="Meson not supported in Py2")
+class AndroidMesonToolchainCrossTestCase(unittest.TestCase):
+
+    def setUp(self):
+        if not self._ndk:
+            raise unittest.SkipTest("requires Android NDK")
+
+    @property
+    def _ndk(self):
+        return os.getenv("ANDROID_NDK")
+
+    @property
+    def _ndk_bin(self):
+        host = "%s-x86_64" % platform.system().lower()
+        return os.path.join(self._ndk, "toolchains", "llvm", "prebuilt", host, "bin")
+
+    @property
+    def _api_level(self):
+        return "21"
+
+    def settings(self):
+        return [("os", "Android"),
+                ("os.api_level", self._api_level),
+                ("arch", self.arch),
+                ("compiler", "clang"),
+                ("compiler.version", "9"),
+                ("compiler.libcxx", "c++_shared")]
+
+    @property
+    def _target(self):
+        return {'armv7': 'armv7a-linux-androideabi%s',
+                'armv8': 'aarch64-linux-android%s',
+                'x86': 'i686-linux-android%s',
+                'x86_64': 'x86_64-linux-android%s'}.get(self.arch) % self._api_level
+
+    @property
+    def _prefix(self):
+        return {'armv7': 'arm-linux-androideabi',
+                'armv8': 'aarch64-linux-android',
+                'x86': 'i686-linux-android',
+                'x86_64': 'x86_64-linux-android'}.get(self.arch)
+
+    def _tool(self, name):
+        return os.path.join(self._ndk_bin, "%s-%s" % (self._prefix, name))
+
+    def env(self):
+        cc = os.path.join(self._ndk_bin, "clang")
+        cxx = os.path.join(self._ndk_bin, "clang++")
+        ar = self._tool('ar')
+        cflags = '--target=%s' % self._target
+        cxxflags = '--target=%s' % self._target
+        ldflags = '--target=%s' % self._target
+
+        return {'CC': cc,
+                'CXX': cxx,
+                'AR': ar,
+                'CFLAGS': cflags,
+                'CXXFLAGS': cxxflags,
+                'LDFLAGS': ldflags}
+
+    def profile(self):
+        template = textwrap.dedent("""
+            include(default)
+            [settings]
+            {settings}
+            [env]
+            {env}
+            """)
+        settings = '\n'.join(["%s = %s" % (s[0], s[1]) for s in self.settings()])
+        env = '\n'.join(["%s = %s" % (k, v) for k, v in self.env().items()])
+        return template.format(settings=settings, env=env)
+
+    @parameterized.expand([('armv8', 'aarch64', 'ELF64', 'AArch64'),
+                           ('armv7', 'arm', 'ELF32', 'ARM'),
+                           ('x86', 'i386', 'ELF32', 'Intel 80386'),
+                           ('x86_64', 'i386:x86-64', 'ELF64', 'Advanced Micro Devices X86-64')
+                           ])
+    def test_android_meson_toolchain_cross_compiling(self, arch, expected_arch,
+                                                     expected_class, expected_machine):
+        self.arch = arch
+
+        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": _conanfile_py,
+                     "meson.build": _meson_build,
+                     "meson_options.txt": _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))
+
+        readelf = self._tool('readelf')
+        objdump = self._tool('objdump')
+
+        self.t.run_command('"%s" -f "%s"' % (objdump, libhello))
+        self.assertIn("architecture: %s" % expected_arch, self.t.out)
+
+        self.t.run_command('"%s" -h "%s"' % (readelf, demo))
+        self.assertIn("Class:                             %s" % expected_class, self.t.out)
+        self.assertIn("OS/ABI:                            UNIX - System V", self.t.out)
+        self.assertIn("Machine:                           %s" % expected_machine, self.t.out)
diff --git a/conans/test/functional/toolchains/meson/test_cross_x86.py b/conans/test/functional/toolchains/meson/test_cross_x86.py
deleted file mode 100644
--- a/conans/test/functional/toolchains/meson/test_cross_x86.py
+++ /dev/null
@@ -1,67 +0,0 @@
-import os
-import platform
-import sys
-import textwrap
-
-import pytest
-
-from conans.test.assets.sources import gen_function_cpp
-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")
-# for Linux, build for x86 will require a multilib compiler
-# for macOS, build for x86 is no longer supported by modern Xcode
-@pytest.mark.skipif(platform.system() != "Windows", reason="requires Windows")
-def test_cross_x86():
-    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')
-        executable('demo', 'main.cpp')
-        """)
-    main_cpp = gen_function_cpp(name="main")
-    profile_x86 = textwrap.dedent("""
-        include(default)
-        [settings]
-        arch=x86
-        [buildenv]
-        CC=cl
-        CXX=cl
-        """)
-
-    client = TestClient()
-    client.save({"conanfile.py": conanfile_py,
-                 "meson.build": meson_build,
-                 "main.cpp": main_cpp,
-                 "x86": profile_x86})
-    profile_str = "--profile:build=default --profile:host=x86"
-    client.run("install . %s" % profile_str)
-    client.run("build .")
-    client.run_command(os.path.join("build", "demo"))
-
-    assert "main _M_IX86 defined" in client.out
-    assert "main _MSC_VER19" in client.out
-    assert "main _MSVC_LANG2014" in client.out
diff --git a/conans/test/functional/toolchains/meson/test_install.py b/conans/test/functional/toolchains/meson/test_install.py
--- a/conans/test/functional/toolchains/meson/test_install.py
+++ b/conans/test/functional/toolchains/meson/test_install.py
@@ -11,7 +11,7 @@ class MesonInstall(TestMesonBase):
     _conanfile_py = textwrap.dedent("""
         import os
         import shutil
-        from conans import ConanFile, tools
+        from conan import ConanFile
         from conan.tools.meson import Meson, MesonToolchain
 
 
@@ -25,10 +25,13 @@ def config_options(self):
                 if self.settings.os == "Windows":
                     del self.options.fPIC
 
+            def layout(self):
+                self.folders.build = "build"
+
             def generate(self):
                 tc = MesonToolchain(self)
                 # https://mesonbuild.com/Release-notes-for-0-50-0.html#libdir-defaults-to-lib-when-cross-compiling
-                tc.definitions["libdir"] = "lib"
+                tc.project_options["libdir"] = "lib"
                 tc.generate()
 
             def build(self):
@@ -38,7 +41,6 @@ def build(self):
 
             def package(self):
                 meson = Meson(self)
-                meson.configure()
                 meson.install()
 
                 # https://mesonbuild.com/FAQ.html#why-does-building-my-project-with-msvc-output-static-libraries-called-libfooa
@@ -58,12 +60,18 @@ def package_info(self):
 
     _test_package_conanfile_py = textwrap.dedent("""
         import os
-        from conans import ConanFile, CMake, tools
+        from conan import ConanFile
+        from conan.tools.cmake import CMake
+        from conan.tools.layout import cmake_layout
 
+        from conans import tools
 
         class TestConan(ConanFile):
             settings = "os", "compiler", "build_type", "arch"
-            generators = "cmake"
+            generators = "CMakeToolchain", "CMakeDeps"
+
+            def layout(self):
+                cmake_layout(self)
 
             def build(self):
                 cmake = CMake(self)
@@ -72,18 +80,18 @@ def build(self):
 
             def test(self):
                 if not tools.cross_building(self):
-                    self.run(os.path.join("bin", "test_package"), run_environment=True)
+                    cmd = os.path.join(self.cpp.build.bindirs[0], "test_package")
+                    self.run(cmd)
         """)
 
     _test_package_cmake_lists = textwrap.dedent("""
         cmake_minimum_required(VERSION 3.1)
         project(test_package CXX)
 
-        include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
-        conan_basic_setup()
+        find_package(hello CONFIG REQUIRED)
 
         add_executable(${PROJECT_NAME} test_package.cpp)
-        target_link_libraries(${PROJECT_NAME} ${CONAN_LIBS})
+        target_link_libraries(${PROJECT_NAME} hello::hello)
         """)
 
     @pytest.mark.tool_meson
diff --git a/conans/test/functional/toolchains/meson/test_ios.py b/conans/test/functional/toolchains/meson/test_ios.py
deleted file mode 100644
--- a/conans/test/functional/toolchains/meson/test_ios.py
+++ /dev/null
@@ -1,131 +0,0 @@
-import os
-import platform
-import sys
-import textwrap
-import unittest
-
-import pytest
-from parameterized import parameterized
-
-from conans.client.tools.apple import XCRun, apple_deployment_target_flag, to_apple_arch
-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 IOSMesonTestCase(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", self.os),
-                ("os.version", self.os_version),
-                ("arch", self.arch),
-                ("compiler", "apple-clang"),
-                ("compiler.version", "12.0"),
-                ("compiler.libcxx", "libc++")]
-
-    def env(self):
-        cc = self.xcrun.cc
-        cxx = self.xcrun.cxx
-
-        deployment_flag = apple_deployment_target_flag(self.os, self.os_version)
-        sysroot_flag = " -isysroot " + self.xcrun.sdk_path
-        arch_flag = " -arch " + to_apple_arch(self.arch)
-        flags = deployment_flag + sysroot_flag + arch_flag
-
-        return {'CC': cc,
-                'CXX': cxx,
-                'CFLAGS': flags,
-                'CXXFLAGS': flags,
-                'LDFLAGS': flags}
-
-    def profile(self):
-        template = textwrap.dedent("""
-            include(default)
-            [settings]
-            {settings}
-            [buildenv]
-            {env}
-            """)
-        settings = '\n'.join(["%s = %s" % (s[0], s[1]) for s in self.settings()])
-        env = '\n'.join(["%s=%s" % (k, v) for k, v in self.env().items()])
-        return template.format(settings=settings, env=env)
-
-    @parameterized.expand([('armv8', 'iOS', '10.0', 'iphoneos'),
-                           ('armv7', 'iOS', '10.0', 'iphoneos'),
-                           ('x86', 'iOS', '10.0', 'iphonesimulator'),
-                           ('x86_64', 'iOS', '10.0', 'iphonesimulator')
-                           ])
-    def test_meson_toolchain(self, arch, os_, os_version, sdk):
-        self.xcrun = XCRun(None, sdk)
-        self.arch = arch
-        self.os = os_
-        self.os_version = os_version
-
-        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: %s" % to_apple_arch(self.arch), self.t.out)
-
-        self.t.run_command('"%s" -info "%s"' % (lipo, demo))
-        self.assertIn("architecture: %s" % to_apple_arch(self.arch), self.t.out)
diff --git a/conans/test/functional/toolchains/meson/test_meson.py b/conans/test/functional/toolchains/meson/test_meson.py
--- a/conans/test/functional/toolchains/meson/test_meson.py
+++ b/conans/test/functional/toolchains/meson/test_meson.py
@@ -7,7 +7,7 @@
 
 class MesonToolchainTest(TestMesonBase):
     _conanfile_py = textwrap.dedent("""
-    from conans import ConanFile, tools
+    from conan import ConanFile
     from conan.tools.meson import Meson, MesonToolchain
 
 
@@ -20,13 +20,16 @@ def config_options(self):
             if self.settings.os == "Windows":
                 del self.options.fPIC
 
+        def layout(self):
+            self.folders.build = "build"
+
         def generate(self):
             tc = MesonToolchain(self)
-            tc.definitions["STRING_DEFINITION"] = "Text"
-            tc.definitions["TRUE_DEFINITION"] = True
-            tc.definitions["FALSE_DEFINITION"] = False
-            tc.definitions["INT_DEFINITION"] = 42
-            tc.definitions["ARRAY_DEFINITION"] = ["Text1", "Text2"]
+            tc.project_options["STRING_DEFINITION"] = "Text"
+            tc.project_options["TRUE_DEFINITION"] = True
+            tc.project_options["FALSE_DEFINITION"] = False
+            tc.project_options["INT_DEFINITION"] = 42
+            tc.project_options["ARRAY_DEFINITION"] = ["Text1", "Text2"]
             tc.generate()
 
         def build(self):
diff --git a/conans/test/functional/toolchains/meson/test_meson_build_require.py b/conans/test/functional/toolchains/meson/test_meson_build_require.py
--- a/conans/test/functional/toolchains/meson/test_meson_build_require.py
+++ b/conans/test/functional/toolchains/meson/test_meson_build_require.py
@@ -1,5 +1,3 @@
-import pytest
-
 from conans.test.assets.genconanfile import GenConanfile
 from conans.test.utils.tools import TestClient
 
@@ -10,9 +8,9 @@ def test_env_vars_from_build_require():
     def package_info(self):
         {}
     """
-    vars = ["CC", "CC_LD", "CXX", "CXX_LD", "AR", "STRIP", "AS", "WINDRES", "PKG_CONFIG", "LD"]
+    vs = ["CC", "CC_LD", "CXX", "CXX_LD", "AR", "STRIP", "AS", "WINDRES", "PKG_CONFIG", "LD"]
     lines = "\n        ".join(['self.buildenv_info.define("{var}", "{var}_VALUE")'.format(var=var)
-                               for var in vars])
+                               for var in vs])
     cf = br.format(lines)
 
     client = TestClient()
diff --git a/conans/test/functional/toolchains/meson/test_pkg_config_reuse.py b/conans/test/functional/toolchains/meson/test_pkg_config_reuse.py
--- a/conans/test/functional/toolchains/meson/test_pkg_config_reuse.py
+++ b/conans/test/functional/toolchains/meson/test_pkg_config_reuse.py
@@ -10,7 +10,7 @@
 @pytest.mark.tool_pkg_config
 class MesonPkgConfigTest(TestMesonBase):
     _conanfile_py = textwrap.dedent("""
-    from conans import ConanFile, tools
+    from conan import ConanFile
     from conan.tools.meson import Meson, MesonToolchain
 
 
@@ -19,6 +19,9 @@ class App(ConanFile):
         generators = "pkg_config"
         requires = "hello/0.1"
 
+        def layout(self):
+            self.folders.build = "build"
+
         def generate(self):
             tc = MesonToolchain(self)
             tc.generate()
@@ -38,9 +41,7 @@ def build(self):
     def test_reuse(self):
         self.t.run("new hello/0.1 -s")
         self.t.run("create . hello/0.1@ %s" % self._settings_str)
-
         app = gen_function_cpp(name="main", includes=["hello"], calls=["hello"])
-
         # Prepare the actual consumer package
         self.t.save({"conanfile.py": self._conanfile_py,
                      "meson.build": self._meson_build,
@@ -50,7 +51,6 @@ def test_reuse(self):
         # Build in the cache
         self.t.run("install . %s" % self._settings_str)
         self.assertIn("conanfile.py: Generator pkg_config created hello.pc", self.t.out)
-
         self.t.run("build .")
         self.t.run_command(os.path.join("build", "demo"))
 
diff --git a/conans/test/functional/toolchains/meson/test_preprocessor_definitions.py b/conans/test/functional/toolchains/meson/test_preprocessor_definitions.py
--- a/conans/test/functional/toolchains/meson/test_preprocessor_definitions.py
+++ b/conans/test/functional/toolchains/meson/test_preprocessor_definitions.py
@@ -7,7 +7,7 @@
 
 class MesonPreprocessorDefinitionsTest(TestMesonBase):
     _conanfile_py = textwrap.dedent("""
-    from conans import ConanFile, tools
+    from conan import ConanFile
     from conan.tools.meson import Meson, MesonToolchain
 
 
@@ -20,6 +20,9 @@ def config_options(self):
             if self.settings.os == "Windows":
                 del self.options.fPIC
 
+        def layout(self):
+            self.folders.build = "build"
+
         def generate(self):
             tc = MesonToolchain(self)
             tc.preprocessor_definitions["TEST_DEFINITION1"] = "TestPpdValue1"
diff --git a/conans/test/functional/toolchains/meson/test_test.py b/conans/test/functional/toolchains/meson/test_test.py
--- a/conans/test/functional/toolchains/meson/test_test.py
+++ b/conans/test/functional/toolchains/meson/test_test.py
@@ -18,7 +18,7 @@ class MesonTest(TestMesonBase):
 
     _test_package_conanfile_py = textwrap.dedent("""
         import os
-        from conans import ConanFile
+        from conan import ConanFile
         from conan.tools.meson import Meson, MesonToolchain
 
 
@@ -26,6 +26,9 @@ class TestConan(ConanFile):
             settings = "os", "compiler", "build_type", "arch"
             generators = "pkg_config"
 
+            def layout(self):
+                self.folders.build = "build"
+
             def generate(self):
                 tc = MesonToolchain(self)
                 tc.generate()
diff --git a/conans/test/integration/toolchains/meson/test_mesontoolchain.py b/conans/test/integration/toolchains/meson/test_mesontoolchain.py
new file mode 100644
--- /dev/null
+++ b/conans/test/integration/toolchains/meson/test_mesontoolchain.py
@@ -0,0 +1,104 @@
+import sys
+import textwrap
+
+import pytest
+
+from conan.tools.meson import MesonToolchain
+from conans.test.utils.tools import TestClient
+
+
+build_env_1 = textwrap.dedent("""
+CFLAGS=-mios-version-min=1 -isysroot ROOT1 -arch armv1
+CXXFLAGS=-mios-version-min=2 -isysroot ROOT2 -arch armv2
+LDFLAGS=-mios-version-min=3 -isysroot ROOT3 -arch armv3
+""")
+
+expected_args_1 = textwrap.dedent("""
+c_args = ['-mios-version-min=1', '-isysroot', 'ROOT1', '-arch', 'armv1'] + preprocessor_definitions
+c_link_args = ['-mios-version-min=3', '-isysroot', 'ROOT3', '-arch', 'armv3']
+cpp_args = ['-mios-version-min=2', '-isysroot', 'ROOT2', '-arch', 'armv2'] + preprocessor_definitions
+cpp_link_args = ['-mios-version-min=3', '-isysroot', 'ROOT3', '-arch', 'armv3']
+""")
+
+build_env_2 = textwrap.dedent("""
+CFLAGS=-isysroot ROOT1 -arch armv1
+CXXFLAGS=-mios-version-min=2 -arch armv2
+LDFLAGS=-mios-version-min=3 -isysroot ROOT3
+""")
+
+expected_args_2 = textwrap.dedent("""
+c_args = ['-isysroot', 'ROOT1', '-arch', 'armv1', '-mios-version-min=10.0'] + preprocessor_definitions
+c_link_args = ['-mios-version-min=3', '-isysroot', 'ROOT3', '-arch', 'arm64']
+cpp_args = ['-mios-version-min=2', '-arch', 'armv2', '-isysroot', '/my/sdk/path'] + preprocessor_definitions
+cpp_link_args = ['-mios-version-min=3', '-isysroot', 'ROOT3', '-arch', 'arm64']
+""")
+
+
+build_env_3 = textwrap.dedent("""
+CFLAGS=-flag1
+CXXFLAGS=-flag2
+LDFLAGS=-flag3
+""")
+
+expected_args_3 = textwrap.dedent("""
+c_args = ['-flag1', '-mios-version-min=10.0', '-isysroot', '/my/sdk/path', '-arch', 'arm64'] + preprocessor_definitions
+c_link_args = ['-flag3', '-mios-version-min=10.0', '-isysroot', '/my/sdk/path', '-arch', 'arm64']
+cpp_args = ['-flag2', '-mios-version-min=10.0', '-isysroot', '/my/sdk/path', '-arch', 'arm64'] + preprocessor_definitions
+cpp_link_args = ['-flag3', '-mios-version-min=10.0', '-isysroot', '/my/sdk/path', '-arch', 'arm64']
+""")
+
+
+@pytest.mark.skipif(sys.version_info.major == 2, reason="Meson not supported in Py2")
+@pytest.mark.parametrize("build_env,expected_args", [
+    (build_env_1, expected_args_1),
+    (build_env_2, expected_args_2),
+    (build_env_3, expected_args_3),
+])
+def test_apple_meson_keep_user_flags(build_env, expected_args):
+    default = textwrap.dedent("""
+    [settings]
+    os=Macos
+    arch=x86_64
+    compiler=apple-clang
+    compiler.version=12.0
+    compiler.libcxx=libc++
+    build_type=Release
+    """)
+
+    cross = textwrap.dedent("""
+    [settings]
+    os = iOS
+    os.version = 10.0
+    os.sdk = iphoneos
+    arch = armv8
+    compiler = apple-clang
+    compiler.version = 12.0
+    compiler.libcxx = libc++
+
+    [buildenv]
+    {build_env}
+
+    [conf]
+    tools.meson.mesontoolchain:sdk_path=/my/sdk/path
+    """.format(build_env=build_env))
+
+    _conanfile_py = textwrap.dedent("""
+    from conan import ConanFile
+    from conan.tools.meson import MesonToolchain
+
+    class App(ConanFile):
+        settings = "os", "arch", "compiler", "build_type"
+
+        def generate(self):
+            tc = MesonToolchain(self)
+            tc.generate()
+    """)
+
+    t = TestClient()
+    t.save({"conanfile.py": _conanfile_py,
+            "build_prof": default,
+            "host_prof": cross})
+
+    t.run("install . -pr:h host_prof -pr:b build_prof")
+    content = t.load(MesonToolchain.cross_filename)
+    assert expected_args in content