File indexing completed on 2025-02-16 05:12:09

0001 import os
0002 import platform
0003 
0004 
0005 from conans.client import tools
0006 from conans.client.build import defs_to_string, join_arguments
0007 from conans.client.build.autotools_environment import AutoToolsBuildEnvironment
0008 from conans.client.build.cppstd_flags import cppstd_from_settings
0009 from conans.client.tools.env import environment_append, _environment_add
0010 from conans.client.tools.oss import args_to_string
0011 from conans.errors import ConanException
0012 from conans.model.build_info import DEFAULT_BIN, DEFAULT_INCLUDE, DEFAULT_LIB
0013 from conans.model.version import Version
0014 from conans.util.conan_v2_mode import conan_v2_error
0015 from conans.util.env_reader import get_env
0016 from conans.util.files import decode_text, get_abs_path, mkdir
0017 from conans.util.runners import version_runner
0018 
0019 
0020 class Meson(object):
0021 
0022     def __init__(self, conanfile, backend=None, build_type=None, append_vcvars=False):
0023         """
0024         :param conanfile: Conanfile instance (or settings for retro compatibility)
0025         :param backend: Generator name to use or none to autodetect.
0026                Possible values: ninja,vs,vs2010,vs2015,vs2017,xcode
0027         :param build_type: Overrides default build type comming from settings
0028         """
0029         self._conanfile = conanfile
0030         self._settings = conanfile.settings
0031         self._append_vcvars = append_vcvars
0032 
0033         self._os = self._ss("os")
0034 
0035         self._compiler = self._ss("compiler")
0036         conan_v2_error("compiler setting should be defined.", not self._compiler)
0037 
0038         self._compiler_version = self._ss("compiler.version")
0039 
0040         self._build_type = self._ss("build_type")
0041 
0042         self.backend = backend or "ninja"  # Other backends are poorly supported, not default other.
0043 
0044         self.options = dict()
0045         if self._conanfile.package_folder:
0046             self.options['prefix'] = self._conanfile.package_folder
0047         self.options['libdir'] = DEFAULT_LIB
0048         self.options['bindir'] = DEFAULT_BIN
0049         self.options['sbindir'] = DEFAULT_BIN
0050         self.options['libexecdir'] = DEFAULT_BIN
0051         self.options['includedir'] = DEFAULT_INCLUDE
0052 
0053         # C++ standard
0054         cppstd = cppstd_from_settings(self._conanfile.settings)
0055         cppstd_conan2meson = {
0056             '98': 'c++03', 'gnu98': 'gnu++03',
0057             '11': 'c++11', 'gnu11': 'gnu++11',
0058             '14': 'c++14', 'gnu14': 'gnu++14',
0059             '17': 'c++17', 'gnu17': 'gnu++17',
0060             '20': 'c++1z', 'gnu20': 'gnu++1z'
0061         }
0062 
0063         if cppstd:
0064             self.options['cpp_std'] = cppstd_conan2meson[cppstd]
0065 
0066         # shared
0067         shared = self._so("shared")
0068         self.options['default_library'] = "shared" if shared is None or shared else "static"
0069 
0070         # fpic
0071         if self._os and "Windows" not in self._os:
0072             fpic = self._so("fPIC")
0073             if fpic is not None:
0074                 shared = self._so("shared")
0075                 self.options['b_staticpic'] = "true" if (fpic or shared) else "false"
0076 
0077         self.build_dir = None
0078         if build_type and build_type != self._build_type:
0079             # Call the setter to warn and update the definitions if needed
0080             self.build_type = build_type
0081 
0082     def _ss(self, setname):
0083         """safe setting"""
0084         return self._conanfile.settings.get_safe(setname)
0085 
0086     def _so(self, setname):
0087         """safe option"""
0088         return self._conanfile.options.get_safe(setname)
0089 
0090     @property
0091     def build_type(self):
0092         return self._build_type
0093 
0094     @build_type.setter
0095     def build_type(self, build_type):
0096         settings_build_type = self._settings.get_safe("build_type")
0097         if build_type != settings_build_type:
0098             self._conanfile.output.warn(
0099                 'Set build type "%s" is different than the settings build_type "%s"'
0100                 % (build_type, settings_build_type))
0101         self._build_type = build_type
0102 
0103     @property
0104     def build_folder(self):
0105         return self.build_dir
0106 
0107     @build_folder.setter
0108     def build_folder(self, value):
0109         self.build_dir = value
0110 
0111     def _get_dirs(self, source_folder, build_folder, source_dir, build_dir, cache_build_folder):
0112         if (source_folder or build_folder) and (source_dir or build_dir):
0113             raise ConanException("Use 'build_folder'/'source_folder'")
0114 
0115         if source_dir or build_dir:  # OLD MODE
0116             build_ret = build_dir or self.build_dir or self._conanfile.build_folder
0117             source_ret = source_dir or self._conanfile.source_folder
0118         else:
0119             build_ret = get_abs_path(build_folder, self._conanfile.build_folder)
0120             source_ret = get_abs_path(source_folder, self._conanfile.source_folder)
0121 
0122         if self._conanfile.in_local_cache and cache_build_folder:
0123             build_ret = get_abs_path(cache_build_folder, self._conanfile.build_folder)
0124 
0125         return source_ret, build_ret
0126 
0127     @property
0128     def flags(self):
0129         return defs_to_string(self.options)
0130 
0131     def configure(self, args=None, defs=None, source_dir=None, build_dir=None,
0132                   pkg_config_paths=None, cache_build_folder=None,
0133                   build_folder=None, source_folder=None):
0134         if not self._conanfile.should_configure:
0135             return
0136         args = args or []
0137         defs = defs or {}
0138 
0139         # overwrite default values with user's inputs
0140         self.options.update(defs)
0141 
0142         source_dir, self.build_dir = self._get_dirs(source_folder, build_folder,
0143                                                     source_dir, build_dir,
0144                                                     cache_build_folder)
0145 
0146         if pkg_config_paths:
0147             pc_paths = os.pathsep.join(get_abs_path(f, self._conanfile.install_folder)
0148                                        for f in pkg_config_paths)
0149         else:
0150             pc_paths = self._conanfile.install_folder
0151 
0152         mkdir(self.build_dir)
0153 
0154         bt = {"RelWithDebInfo": "debugoptimized",
0155               "MinSizeRel": "release",
0156               "Debug": "debug",
0157               "Release": "release"}.get(str(self.build_type), "")
0158 
0159         build_type = "--buildtype=%s" % bt
0160         arg_list = join_arguments([
0161             "--backend=%s" % self.backend,
0162             self.flags,
0163             args_to_string(args),
0164             build_type
0165         ])
0166         command = 'meson "%s" "%s" %s' % (source_dir, self.build_dir, arg_list)
0167         with environment_append({"PKG_CONFIG_PATH": pc_paths}):
0168             self._run(command)
0169 
0170     @property
0171     def _vcvars_needed(self):
0172         return (self._compiler == "Visual Studio" and self.backend == "ninja" and
0173                 platform.system() == "Windows")
0174 
0175     def _run(self, command):
0176         def _build():
0177             env_build = AutoToolsBuildEnvironment(self._conanfile)
0178             with environment_append(env_build.vars):
0179                 self._conanfile.run(command)
0180 
0181         if self._vcvars_needed:
0182             vcvars_dict = tools.vcvars_dict(self._settings, output=self._conanfile.output)
0183             with _environment_add(vcvars_dict, post=self._append_vcvars):
0184                 _build()
0185         else:
0186             _build()
0187 
0188     def _run_meson_targets(self, args=None, build_dir=None, targets=None):
0189         args = args or []
0190         build_dir = build_dir or self.build_dir or self._conanfile.build_folder
0191 
0192         arg_list = join_arguments([
0193             '-C "%s"' % build_dir,
0194             args_to_string(args),
0195             args_to_string(targets)
0196         ])
0197         # FIXME: We are assuming for other backends that meson version is > 0.55.0
0198         #        so you can use new command "meson compile"
0199         command = "ninja" if self.backend == "ninja" else "meson compile"
0200         self._run("%s %s" % (command, arg_list))
0201 
0202     def _run_meson_command(self, subcommand=None, args=None, build_dir=None):
0203         args = args or []
0204         build_dir = build_dir or self.build_dir or self._conanfile.build_folder
0205 
0206         arg_list = join_arguments([
0207             subcommand,
0208             '-C "%s"' % build_dir,
0209             args_to_string(args)
0210         ])
0211         self._run("meson %s" % arg_list)
0212 
0213     def build(self, args=None, build_dir=None, targets=None):
0214         if not self._conanfile.should_build:
0215             return
0216         conan_v2_error("build_type setting should be defined.", not self._build_type)
0217         self._run_meson_targets(args=args, build_dir=build_dir, targets=targets)
0218 
0219     def install(self, args=None, build_dir=None):
0220         if not self._conanfile.should_install:
0221             return
0222         mkdir(self._conanfile.package_folder)
0223         if not self.options.get('prefix'):
0224             raise ConanException("'prefix' not defined for 'meson.install()'\n"
0225                                  "Make sure 'package_folder' is defined")
0226         self._run_meson_targets(args=args, build_dir=build_dir, targets=["install"])
0227 
0228     def test(self, args=None, build_dir=None, targets=None):
0229         if not self._conanfile.should_test or not get_env("CONAN_RUN_TESTS", True) or \
0230            self._conanfile.conf["tools.build:skip_test"]:
0231             return
0232         if not targets:
0233             targets = ["test"]
0234         self._run_meson_targets(args=args, build_dir=build_dir, targets=targets)
0235 
0236     def meson_install(self, args=None, build_dir=None):
0237         if not self._conanfile.should_install:
0238             return
0239         self._run_meson_command(subcommand='install', args=args, build_dir=build_dir)
0240 
0241     def meson_test(self, args=None, build_dir=None):
0242         if not self._conanfile.should_test or not get_env("CONAN_RUN_TESTS", True) or \
0243            self._conanfile.conf["tools.build:skip_test"]:
0244             return
0245         self._run_meson_command(subcommand='test', args=args, build_dir=build_dir)
0246 
0247     @staticmethod
0248     def get_version():
0249         try:
0250             out = version_runner(["meson", "--version"])
0251             version_line = decode_text(out).split('\n', 1)[0]
0252             version_str = version_line.rsplit(' ', 1)[-1]
0253             return Version(version_str)
0254         except Exception as e:
0255             raise ConanException("Error retrieving Meson version: '{}'".format(e))