diff options
45 files changed, 495 insertions, 179 deletions
diff --git a/docs/markdown/snippets/fix_backend_startup_project.md b/docs/markdown/snippets/fix_backend_startup_project.md new file mode 100644 index 0000000..8269ef6 --- /dev/null +++ b/docs/markdown/snippets/fix_backend_startup_project.md @@ -0,0 +1,4 @@ +## backend_startup_project + +`backend_startup_project` will no longer erase the last project in a VS +solution if it is not the specified project. diff --git a/docs/yaml/functions/shared_library.yaml b/docs/yaml/functions/shared_library.yaml index 46e5a1c..15ac782 100644 --- a/docs/yaml/functions/shared_library.yaml +++ b/docs/yaml/functions/shared_library.yaml @@ -2,13 +2,6 @@ name: shared_library returns: lib description: Builds a shared library with the given sources. -notes: - - | - Linking to a shared module is not supported on some - platforms, notably OSX. Consider using a - [[shared_library]] instead, if you need to both - `dlopen()` and link with a library. - posargs_inherit: _build_target_base varargs_inherit: _build_target_base kwargs_inherit: _build_target_base diff --git a/docs/yaml/functions/shared_module.yaml b/docs/yaml/functions/shared_module.yaml index 8909c2f..ff374e7 100644 --- a/docs/yaml/functions/shared_module.yaml +++ b/docs/yaml/functions/shared_module.yaml @@ -13,6 +13,15 @@ description: | you will need to set the `export_dynamic` argument of the executable to `true`. +notes: + - | + *Linking to a shared module is deprecated, and will be an error in the future*. + It used to be allowed because it was the only way to have a shared-library-like target that + contained references to undefined symbols. However, since 0.40.0, the `override_options:` + [[build_target]] keyword argument can be used to create such a [[shared_library]], and shared + modules have other characteristics that make them incompatible with linking, such as a lack of + SONAME. Linking to shared modules also does not work on some platforms, such as on macOS / iOS. + posargs_inherit: _build_target_base varargs_inherit: _build_target_base kwargs_inherit: _build_target_base diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 6c15678..769ee6c 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -395,15 +395,15 @@ class Backend: return os.path.join(self.build_to_src, target_dir) return self.build_to_src - def get_target_private_dir(self, target: build.Target) -> str: + def get_target_private_dir(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]) -> str: return os.path.join(self.get_target_filename(target, warn_multi_output=False) + '.p') - def get_target_private_dir_abs(self, target: build.Target) -> str: + def get_target_private_dir_abs(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]) -> str: return os.path.join(self.environment.get_build_dir(), self.get_target_private_dir(target)) @lru_cache(maxsize=None) def get_target_generated_dir( - self, target: build.Target, + self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex], gensrc: T.Union[build.CustomTarget, build.CustomTargetIndex, build.GeneratedList], src: str) -> str: """ @@ -418,7 +418,8 @@ class Backend: # target that the GeneratedList is used in return os.path.join(self.get_target_private_dir(target), src) - def get_unity_source_file(self, target: build.Target, suffix: str, number: int) -> mesonlib.File: + def get_unity_source_file(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex], + suffix: str, number: int) -> mesonlib.File: # There is a potential conflict here, but it is unlikely that # anyone both enables unity builds and has a file called foo-unity.cpp. osrc = f'{target.name}-unity{number}.{suffix}' @@ -763,18 +764,24 @@ class Backend: paths.append(os.path.join(self.build_to_src, rel_to_src)) else: paths.append(libdir) + for i in chain(target.link_targets, target.link_whole_targets): + if isinstance(i, build.BuildTarget): + paths.extend(self.rpaths_for_bundled_shared_libraries(i, exclude_system)) return paths - def determine_rpath_dirs(self, target: build.BuildTarget) -> T.Tuple[str, ...]: + # This may take other types + def determine_rpath_dirs(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex] + ) -> T.Tuple[str, ...]: result: OrderedSet[str] if self.environment.coredata.get_option(OptionKey('layout')) == 'mirror': - # NEed a copy here + # Need a copy here result = OrderedSet(target.get_link_dep_subdirs()) else: result = OrderedSet() result.add('meson-out') - result.update(self.rpaths_for_bundled_shared_libraries(target)) - target.rpath_dirs_to_remove.update([d.encode('utf-8') for d in result]) + if isinstance(target, build.BuildTarget): + result.update(self.rpaths_for_bundled_shared_libraries(target)) + target.rpath_dirs_to_remove.update([d.encode('utf-8') for d in result]) return tuple(result) @staticmethod diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index a1d3e50..b6621c9 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -2785,7 +2785,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) commands += linker.get_std_shared_lib_link_args() # All shared libraries are PIC commands += linker.get_pic_args() - if not isinstance(target, build.SharedModule): + if not isinstance(target, build.SharedModule) or target.backwards_compat_want_soname: # Add -Wl,-soname arguments on Linux, -install_name on OS X commands += linker.get_soname_args( self.environment, target.prefix, target.name, target.suffix, diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 4597a5a..7a56041 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -98,6 +98,8 @@ class Vs2010Backend(backends.Backend): super().__init__(build, interpreter) self.name = 'vs2010' self.project_file_version = '10.0.30319.1' + self.sln_file_version = '11.00' + self.sln_version_comment = '2010' self.platform_toolset = None self.vs_version = '2010' self.windows_target_platform_version = None @@ -348,10 +350,11 @@ class Vs2010Backend(backends.Backend): def generate_solution(self, sln_filename, projlist): default_projlist = self.get_build_by_default_targets() sln_filename_tmp = sln_filename + '~' - with open(sln_filename_tmp, 'w', encoding='utf-8') as ofile: - ofile.write('Microsoft Visual Studio Solution File, Format ' - 'Version 11.00\n') - ofile.write('# Visual Studio ' + self.vs_version + '\n') + # Note using the utf-8 BOM requires the blank line, otherwise Visual Studio Version Selector fails. + # Without the BOM, VSVS fails if there is a blank line. + with open(sln_filename_tmp, 'w', encoding='utf-8-sig') as ofile: + ofile.write('\nMicrosoft Visual Studio Solution File, Format Version %s\n' % self.sln_file_version) + ofile.write('# Visual Studio %s\n' % self.sln_version_comment) prj_templ = 'Project("{%s}") = "%s", "%s", "{%s}"\n' for prj in projlist: coredata = self.environment.coredata @@ -467,7 +470,7 @@ class Vs2010Backend(backends.Backend): # Put the startup project first in the project list if startup_idx: - projlist = [projlist[startup_idx]] + projlist[0:startup_idx] + projlist[startup_idx + 1:-1] + projlist.insert(0, projlist.pop(startup_idx)) return projlist diff --git a/mesonbuild/backend/vs2012backend.py b/mesonbuild/backend/vs2012backend.py index a9ba5f4..ee2f022 100644 --- a/mesonbuild/backend/vs2012backend.py +++ b/mesonbuild/backend/vs2012backend.py @@ -24,6 +24,8 @@ class Vs2012Backend(Vs2010Backend): super().__init__(build, interpreter) self.name = 'vs2012' self.vs_version = '2012' + self.sln_file_version = '12.00' + self.sln_version_comment = '2012' if self.environment is not None: # TODO: we assume host == build comps = self.environment.coredata.compilers.host diff --git a/mesonbuild/backend/vs2013backend.py b/mesonbuild/backend/vs2013backend.py index 0f2c8bd..37724db 100644 --- a/mesonbuild/backend/vs2013backend.py +++ b/mesonbuild/backend/vs2013backend.py @@ -24,6 +24,8 @@ class Vs2013Backend(Vs2010Backend): super().__init__(build, interpreter) self.name = 'vs2013' self.vs_version = '2013' + self.sln_file_version = '12.00' + self.sln_version_comment = '2013' if self.environment is not None: # TODO: we assume host == build comps = self.environment.coredata.compilers.host diff --git a/mesonbuild/backend/vs2015backend.py b/mesonbuild/backend/vs2015backend.py index bdc1675..4952caf 100644 --- a/mesonbuild/backend/vs2015backend.py +++ b/mesonbuild/backend/vs2015backend.py @@ -24,6 +24,8 @@ class Vs2015Backend(Vs2010Backend): super().__init__(build, interpreter) self.name = 'vs2015' self.vs_version = '2015' + self.sln_file_version = '12.00' + self.sln_version_comment = '14' if self.environment is not None: # TODO: we assume host == build comps = self.environment.coredata.compilers.host diff --git a/mesonbuild/backend/vs2017backend.py b/mesonbuild/backend/vs2017backend.py index 452d7a6..e9f949d 100644 --- a/mesonbuild/backend/vs2017backend.py +++ b/mesonbuild/backend/vs2017backend.py @@ -27,6 +27,8 @@ class Vs2017Backend(Vs2010Backend): super().__init__(build, interpreter) self.name = 'vs2017' self.vs_version = '2017' + self.sln_file_version = '12.00' + self.sln_version_comment = '15' # We assume that host == build if self.environment is not None: comps = self.environment.coredata.compilers.host @@ -59,4 +61,4 @@ class Vs2017Backend(Vs2010Backend): if 'c' in file_args: optargs = [x for x in file_args['c'] if x.startswith('/std:c')] if optargs: - ET.SubElement(clconf, 'LanguageStandard_C').text = optargs[0].replace("/std:c", "stdc")
\ No newline at end of file + ET.SubElement(clconf, 'LanguageStandard_C').text = optargs[0].replace("/std:c", "stdc") diff --git a/mesonbuild/backend/vs2019backend.py b/mesonbuild/backend/vs2019backend.py index a87fa8a..1efadcd 100644 --- a/mesonbuild/backend/vs2019backend.py +++ b/mesonbuild/backend/vs2019backend.py @@ -25,6 +25,8 @@ class Vs2019Backend(Vs2010Backend): def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]): super().__init__(build, interpreter) self.name = 'vs2019' + self.sln_file_version = '12.00' + self.sln_version_comment = 'Version 16' if self.environment is not None: comps = self.environment.coredata.compilers.host if comps and all(c.id == 'clang-cl' for c in comps.values()): diff --git a/mesonbuild/backend/vs2022backend.py b/mesonbuild/backend/vs2022backend.py index 19ad090..b0925a4 100644 --- a/mesonbuild/backend/vs2022backend.py +++ b/mesonbuild/backend/vs2022backend.py @@ -25,6 +25,8 @@ class Vs2022Backend(Vs2010Backend): def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]): super().__init__(build, interpreter) self.name = 'vs2022' + self.sln_file_version = '12.00' + self.sln_version_comment = 'Version 17' if self.environment is not None: comps = self.environment.coredata.compilers.host if comps and all(c.id == 'clang-cl' for c in comps.values()): diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 18c0911..545575c 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -695,7 +695,7 @@ class BuildTarget(Target): self.external_deps: T.List[dependencies.Dependency] = [] self.include_dirs: T.List['IncludeDirs'] = [] self.link_language = kwargs.get('link_language') - self.link_targets: T.List[BuildTarget] = [] + self.link_targets: T.List[T.Union['BuildTarget', 'CustomTarget', 'CustomTargetIndex']] = [] self.link_whole_targets = [] self.link_depends = [] self.added_deps = set() @@ -1588,15 +1588,19 @@ You probably should put it in link_with instead.''') Warn if shared modules are linked with target: (link_with) #2865 ''' for link_target in self.link_targets: - if isinstance(link_target, SharedModule): + if isinstance(link_target, SharedModule) and not link_target.backwards_compat_want_soname: if self.environment.machines[self.for_machine].is_darwin(): raise MesonException( - 'target links against shared modules. This is not permitted on OSX') + f'target {self.name} links against shared module {link_target.name}. This is not permitted on OSX') else: - mlog.warning('target links against shared modules. This ' - 'is not recommended as it is not supported on some ' - 'platforms') - return + mlog.deprecation(f'target {self.name} links against shared module {link_target.name}, which is incorrect.' + '\n ' + f'This will be an error in the future, so please use shared_library() for {link_target.name} instead.' + '\n ' + f'If shared_module() was used for {link_target.name} because it has references to undefined symbols,' + '\n ' + 'use shared_libary() with `override_options: [\'b_lundef=false\']` instead.') + link_target.backwards_compat_want_soname = True class Generator(HoldableObject): def __init__(self, exe: T.Union['Executable', programs.ExternalProgram], @@ -2259,6 +2263,9 @@ class SharedModule(SharedLibrary): raise MesonException('Shared modules must not specify the soversion kwarg.') super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs) self.typename = 'shared module' + # We need to set the soname in cases where build files link the module + # to build targets, see: https://github.com/mesonbuild/meson/issues/9492 + self.backwards_compat_want_soname = False def get_default_install_dir(self, environment) -> T.Tuple[str, str]: return environment.get_shared_module_dir(), '{moduledir_shared}' @@ -2796,7 +2803,7 @@ class Data(HoldableObject): self.data_type = data_type class TestSetup: - def __init__(self, exe_wrapper: T.Optional[T.List[str]], gdb: bool, + def __init__(self, exe_wrapper: T.List[str], gdb: bool, timeout_multiplier: int, env: EnvironmentVariables, exclude_suites: T.List[str]): self.exe_wrapper = exe_wrapper diff --git a/mesonbuild/compilers/d.py b/mesonbuild/compilers/d.py index f550a33..4201d82 100644 --- a/mesonbuild/compilers/d.py +++ b/mesonbuild/compilers/d.py @@ -64,7 +64,7 @@ ldc_optimization_args = {'0': [], '1': ['-O1'], '2': ['-O2'], '3': ['-O3'], - 's': ['-Os'], + 's': ['-Oz'], } # type: T.Dict[str, T.List[str]] dmd_optimization_args = {'0': [], diff --git a/mesonbuild/compilers/mixins/c2000.py b/mesonbuild/compilers/mixins/c2000.py index 65261c6..ab0278e 100644 --- a/mesonbuild/compilers/mixins/c2000.py +++ b/mesonbuild/compilers/mixins/c2000.py @@ -49,7 +49,7 @@ c2000_optimization_args = { c2000_debug_args = { False: [], - True: [] + True: ['-g'] } # type: T.Dict[bool, T.List[str]] @@ -106,9 +106,9 @@ class C2000Compiler(Compiler): result = [] for i in args: if i.startswith('-D'): - i = '-define=' + i[2:] + i = '--define=' + i[2:] if i.startswith('-I'): - i = '-include=' + i[2:] + i = '--include_path=' + i[2:] if i.startswith('-Wl,-rpath='): continue elif i == '--print-search-dirs': @@ -120,8 +120,10 @@ class C2000Compiler(Compiler): def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: for idx, i in enumerate(parameter_list): - if i[:9] == '-include=': - parameter_list[idx] = i[:9] + os.path.normpath(os.path.join(build_dir, i[9:])) + if i[:14] == '--include_path': + parameter_list[idx] = i[:14] + os.path.normpath(os.path.join(build_dir, i[14:])) + if i[:2] == '-I': + parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:])) return parameter_list diff --git a/mesonbuild/compilers/mixins/visualstudio.py b/mesonbuild/compilers/mixins/visualstudio.py index 4bc721f..1ea7219 100644 --- a/mesonbuild/compilers/mixins/visualstudio.py +++ b/mesonbuild/compilers/mixins/visualstudio.py @@ -332,6 +332,8 @@ class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta): return '14.1' # (Visual Studio 2017) elif version < 1930: return '14.2' # (Visual Studio 2019) + elif version < 1940: + return '14.3' # (Visual Studio 2022) mlog.warning(f'Could not find toolset for version {self.version!r}') return None diff --git a/mesonbuild/dependencies/misc.py b/mesonbuild/dependencies/misc.py index eaa018a..43d7feb 100644 --- a/mesonbuild/dependencies/misc.py +++ b/mesonbuild/dependencies/misc.py @@ -452,7 +452,7 @@ class IconvBuiltinDependency(BuiltinDependency): def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]): super().__init__(name, env, kwargs) - if self.clib_compiler.has_function('iconv_open', '', env)[0]: + if self.clib_compiler.has_function('iconv_open', '#include <iconv.h>', env)[0]: self.is_found = True diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index d946678..2e96273 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -56,9 +56,11 @@ from .type_checking import ( CT_INPUT_KW, CT_INSTALL_DIR_KW, CT_OUTPUT_KW, + DEFAULT_OPTIONS, DEPENDS_KW, DEPEND_FILES_KW, DEPFILE_KW, + DISABLER_KW, ENV_KW, INSTALL_KW, INSTALL_MODE_KW, @@ -90,11 +92,21 @@ if T.TYPE_CHECKING: # Input source types passed to Targets SourceInputs = T.Union[mesonlib.File, build.GeneratedList, build.BuildTarget, build.BothLibraries, - build.CustomTargetIndex, build.CustomTarget, build.GeneratedList, str] - # Input source types passed to the build.Target5 classes + build.CustomTargetIndex, build.CustomTarget, build.GeneratedList, + build.ExtractedObjects, str] + # Input source types passed to the build.Target classes SourceOutputs = T.Union[mesonlib.File, build.GeneratedList, build.BuildTarget, build.CustomTargetIndex, build.CustomTarget, - build.GeneratedList] + build.ExtractedObjects, build.GeneratedList] + + +def _project_version_validator(value: T.Union[T.List, str, mesonlib.File, None]) -> T.Optional[str]: + if isinstance(value, list): + if len(value) != 1: + return 'when passed as array must have a length of 1' + elif not isinstance(value[0], (str, mesonlib.File)): + return 'when passed as array must contain a string or File' + return None def stringifyUserArguments(args, quote=False): @@ -111,19 +123,14 @@ def stringifyUserArguments(args, quote=False): raise InvalidArguments('Function accepts only strings, integers, bools, lists, dictionaries and lists thereof.') class Summary: - def __init__(self, project_name, project_version): + def __init__(self, project_name: str, project_version: str): self.project_name = project_name self.project_version = project_version self.sections = collections.defaultdict(dict) self.max_key_len = 0 - def add_section(self, section, values, kwargs, subproject): - bool_yn = kwargs.get('bool_yn', False) - if not isinstance(bool_yn, bool): - raise InterpreterException('bool_yn keyword argument must be boolean') - list_sep = kwargs.get('list_sep') - if list_sep is not None and not isinstance(list_sep, str): - raise InterpreterException('list_sep keyword argument must be string') + def add_section(self, section: str, values: T.Dict[str, T.Any], bool_yn: bool, + list_sep: T.Optional[str], subproject: str) -> None: for k, v in values.items(): if k in self.sections[section]: raise InterpreterException(f'Summary section {section!r} already have key {k!r}') @@ -257,7 +264,7 @@ class Interpreter(InterpreterBase, HoldableObject): self.environment = self.build.environment self.coredata = self.environment.get_coredata() self.backend = backend - self.summary = {} + self.summary: T.Dict[str, 'Summary'] = {} self.modules = {} # Subproject directory is usually the name of the subproject, but can # be different for dependencies provided by wrap files. @@ -565,7 +572,7 @@ class Interpreter(InterpreterBase, HoldableObject): @typed_kwargs( 'import', REQUIRED_KW.evolve(since='0.59.0'), - KwargInfo('disabler', bool, default=False, since='0.59.0'), + DISABLER_KW.evolve(since='0.59.0'), ) @disablerIfNotFound def func_import(self, node: mparser.BaseNode, args: T.Tuple[str], @@ -1019,16 +1026,29 @@ external dependencies (including libraries) must go to "dependencies".''') options = {k: v for k, v in self.environment.options.items() if k.is_backend()} self.coredata.set_options(options) - @permittedKwargs({'version', 'meson_version', 'default_options', 'license', 'subproject_dir'}) @typed_pos_args('project', str, varargs=str) - def func_project(self, node: mparser.FunctionNode, args: T.Tuple[str, T.List[str]], kwargs: 'TYPE_kwargs') -> None: + @typed_kwargs( + 'project', + DEFAULT_OPTIONS, + KwargInfo('meson_version', (str, NoneType)), + KwargInfo( + 'version', + (str, mesonlib.File, NoneType, list), + default='undefined', + validator=_project_version_validator, + convertor=lambda x: x[0] if isinstance(x, list) else x, + ), + KwargInfo('license', ContainerTypeInfo(list, str), default=['unknown'], listify=True), + KwargInfo('subproject_dir', str, default='subprojects'), + ) + def func_project(self, node: mparser.FunctionNode, args: T.Tuple[str, T.List[str]], kwargs: 'kwargs.Project') -> None: proj_name, proj_langs = args if ':' in proj_name: raise InvalidArguments(f"Project name {proj_name!r} must not contain ':'") # This needs to be evaluated as early as possible, as meson uses this # for things like deprecation testing. - if 'meson_version' in kwargs: + if kwargs['meson_version']: cv = coredata.version pv = kwargs['meson_version'] if not mesonlib.version_compare(cv, pv): @@ -1045,8 +1065,8 @@ external dependencies (including libraries) must go to "dependencies".''') # values previously set from command line. That means that changing # default_options in a project will trigger a reconfigure but won't # have any effect. - self.project_default_options = mesonlib.stringlistify(kwargs.get('default_options', [])) - self.project_default_options = coredata.create_options_dict(self.project_default_options, self.subproject) + self.project_default_options = coredata.create_options_dict( + kwargs['default_options'], self.subproject) # If this is the first invocation we always need to initialize # builtins, if this is a subproject that is new in a re-invocation we @@ -1062,11 +1082,8 @@ external dependencies (including libraries) must go to "dependencies".''') if not self.is_subproject(): self.build.project_name = proj_name self.active_projectname = proj_name - version = kwargs.get('version', 'undefined') - if isinstance(version, list): - if len(version) != 1: - raise InvalidCode('Version argument is an array with more than one entry.') - version = version[0] + + version = kwargs['version'] if isinstance(version, mesonlib.File): FeatureNew.single_use('version from file', '0.57.0', self.subproject) self.add_build_def_file(version) @@ -1081,33 +1098,29 @@ external dependencies (including libraries) must go to "dependencies".''') if len(ver_data) != 1: raise InterpreterException('Version file must contain exactly one line of text.') self.project_version = ver_data[0] - elif isinstance(version, str): - self.project_version = version else: - raise InvalidCode('The version keyword argument must be a string or a file.') + self.project_version = version + if self.build.project_version is None: self.build.project_version = self.project_version - proj_license = mesonlib.stringlistify(kwargs.get('license', 'unknown')) + proj_license = kwargs['license'] self.build.dep_manifest[proj_name] = build.DepManifest(self.project_version, proj_license) if self.subproject in self.build.projects: raise InvalidCode('Second call to project().') # spdirname is the subproject_dir for this project, relative to self.subdir. # self.subproject_dir is the subproject_dir for the main project, relative to top source dir. - spdirname = kwargs.get('subproject_dir') - if spdirname: - if not isinstance(spdirname, str): - raise InterpreterException('Subproject_dir must be a string') - if os.path.isabs(spdirname): - raise InterpreterException('Subproject_dir must not be an absolute path.') - if spdirname.startswith('.'): - raise InterpreterException('Subproject_dir must not begin with a period.') - if '..' in spdirname: - raise InterpreterException('Subproject_dir must not contain a ".." segment.') - if not self.is_subproject(): - self.subproject_dir = spdirname - else: - spdirname = 'subprojects' + spdirname = kwargs['subproject_dir'] + if not isinstance(spdirname, str): + raise InterpreterException('Subproject_dir must be a string') + if os.path.isabs(spdirname): + raise InterpreterException('Subproject_dir must not be an absolute path.') + if spdirname.startswith('.'): + raise InterpreterException('Subproject_dir must not begin with a period.') + if '..' in spdirname: + raise InterpreterException('Subproject_dir must not contain a ".." segment.') + if not self.is_subproject(): + self.subproject_dir = spdirname self.build.subproject_dir = self.subproject_dir # Load wrap files from this (sub)project. @@ -1159,7 +1172,7 @@ external dependencies (including libraries) must go to "dependencies".''') tv = FeatureNew.get_target_version(self.subproject) if FeatureNew.check_version(tv, '0.54.0'): mlog.warning('add_languages is missing native:, assuming languages are wanted for both host and build.', - location=self.current_node) + location=node) success = self.add_languages(langs, False, MachineChoice.BUILD) success &= self.add_languages(langs, required, MachineChoice.HOST) @@ -1177,31 +1190,33 @@ external dependencies (including libraries) must go to "dependencies".''') mlog.log(mlog.bold('Message:'), *args) @noArgsFlattening - @FeatureNewKwargs('summary', '0.54.0', ['list_sep']) - @permittedKwargs({'section', 'bool_yn', 'list_sep'}) @FeatureNew('summary', '0.53.0') - def func_summary(self, node, args, kwargs): - if len(args) == 1: + @typed_pos_args('summary', (str, dict), optargs=[object]) + @typed_kwargs( + 'summary', + KwargInfo('section', str, default=''), + KwargInfo('bool_yn', bool, default=False), + KwargInfo('list_sep', (str, NoneType), since='0.54.0') + ) + def func_summary(self, node: mparser.BaseNode, args: T.Tuple[T.Union[str, T.Dict[str, T.Any]], T.Optional[T.Any]], + kwargs: 'kwargs.Summary') -> None: + if args[1] is None: if not isinstance(args[0], dict): raise InterpreterException('Summary first argument must be dictionary.') values = args[0] - elif len(args) == 2: + else: if not isinstance(args[0], str): raise InterpreterException('Summary first argument must be string.') values = {args[0]: args[1]} - else: - raise InterpreterException('Summary accepts at most 2 arguments.') - section = kwargs.get('section', '') - if not isinstance(section, str): - raise InterpreterException('Summary\'s section keyword argument must be string.') - self.summary_impl(section, values, kwargs) + self.summary_impl(kwargs['section'], values, kwargs) - def summary_impl(self, section, values, kwargs): + def summary_impl(self, section: str, values, kwargs: 'kwargs.Summary') -> None: if self.subproject not in self.summary: self.summary[self.subproject] = Summary(self.active_projectname, self.project_version) - self.summary[self.subproject].add_section(section, values, kwargs, self.subproject) + self.summary[self.subproject].add_section( + section, values, kwargs['bool_yn'], kwargs['list_sep'], self.subproject) - def _print_summary(self): + def _print_summary(self) -> None: # Add automatic 'Supbrojects' section in main project. all_subprojects = collections.OrderedDict() for name, subp in sorted(self.subprojects.items()): @@ -1228,7 +1243,7 @@ external dependencies (including libraries) must go to "dependencies".''') sorted_options = sorted(self.user_defined_options.cmd_line_options.items()) values.update({str(k): v for k, v in sorted_options}) if values: - self.summary_impl('User defined options', values, {}) + self.summary_impl('User defined options', values, {'bool_yn': False, 'list_sep': None}) # Print all summaries, main project last. mlog.log('') # newline main_summary = self.summary.pop('', None) @@ -1395,9 +1410,13 @@ external dependencies (including libraries) must go to "dependencies".''') # TODO update modules to always pass `for_machine`. It is bad-form to assume # the host machine. - def find_program_impl(self, args, for_machine: MachineChoice = MachineChoice.HOST, - required=True, silent=True, wanted='', search_dirs=None, - version_func=None): + def find_program_impl(self, args: T.List[mesonlib.FileOrString], + for_machine: MachineChoice = MachineChoice.HOST, + required: bool = True, silent: bool = True, + wanted: T.Union[str, T.List[str]] = '', + search_dirs: T.Optional[T.List[str]] = None, + version_func: T.Optional[T.Callable[[T.Union['ExternalProgram', 'build.Executable', 'OverrideProgram']], str]] = None + ) -> T.Union['ExternalProgram', 'build.Executable', 'OverrideProgram']: args = mesonlib.listify(args) extra_info = [] @@ -1421,7 +1440,7 @@ external dependencies (including libraries) must go to "dependencies".''') interp = self.subprojects[progobj.subproject].held_object assert isinstance(interp, Interpreter) version = interp.project_version - elif isinstance(progobj, ExternalProgram): + else: version = progobj.get_version(self) is_found, not_found, found = mesonlib.version_compare_many(version, wanted) if not is_found: @@ -1471,25 +1490,27 @@ external dependencies (including libraries) must go to "dependencies".''') self.do_subproject(fallback, 'meson', sp_kwargs) return self.program_from_overrides(args, extra_info) - @FeatureNewKwargs('find_program', '0.53.0', ['dirs']) - @FeatureNewKwargs('find_program', '0.52.0', ['version']) - @FeatureNewKwargs('find_program', '0.49.0', ['disabler']) + @typed_pos_args('find_program', varargs=(str, mesonlib.File), min_varargs=1) + @typed_kwargs( + 'find_program', + DISABLER_KW.evolve(since='0.49.0'), + NATIVE_KW, + REQUIRED_KW, + KwargInfo('dirs', ContainerTypeInfo(list, str), default=[], listify=True, since='0.53.0'), + KwargInfo('version', ContainerTypeInfo(list, str), default=[], listify=True, since='0.52.0'), + ) @disablerIfNotFound - @permittedKwargs({'required', 'native', 'version', 'dirs'}) - def func_find_program(self, node, args, kwargs) -> T.Union['build.Executable', ExternalProgram, 'OverrideProgram']: - if not args: - raise InterpreterException('No program name specified.') - + def func_find_program(self, node: mparser.BaseNode, args: T.Tuple[T.List[mesonlib.FileOrString]], + kwargs: 'kwargs.FindProgram', + ) -> T.Union['build.Executable', ExternalProgram, 'OverrideProgram']: disabled, required, feature = extract_required_kwarg(kwargs, self.subproject) if disabled: - mlog.log('Program', mlog.bold(' '.join(args)), 'skipped: feature', mlog.bold(feature), 'disabled') - return self.notfound_program(args) + mlog.log('Program', mlog.bold(' '.join(args[0])), 'skipped: feature', mlog.bold(feature), 'disabled') + return self.notfound_program(args[0]) search_dirs = extract_search_dirs(kwargs) - wanted = mesonlib.stringlistify(kwargs.get('version', [])) - for_machine = self.machine_from_native_kwarg(kwargs) - return self.find_program_impl(args, for_machine, required=required, - silent=False, wanted=wanted, + return self.find_program_impl(args[0], kwargs['native'], required=required, + silent=False, wanted=kwargs['version'], search_dirs=search_dirs) def func_find_library(self, node, args, kwargs): @@ -1731,11 +1752,11 @@ external dependencies (including libraries) must go to "dependencies".''') kwargs['input'] = self.source_strings_to_files(extract_as_list(kwargs, 'input')) except mesonlib.MesonException: mlog.warning(f'''Custom target input '{kwargs['input']}' can't be converted to File object(s). -This will become a hard error in the future.''', location=self.current_node) +This will become a hard error in the future.''', location=node) kwargs['env'] = self.unpack_env_kwarg(kwargs) if 'command' in kwargs and isinstance(kwargs['command'], list) and kwargs['command']: if isinstance(kwargs['command'][0], str): - kwargs['command'][0] = self.func_find_program(node, kwargs['command'][0], {}) + kwargs['command'][0] = self.find_program_impl([kwargs['command'][0]]) tg = build.CustomTarget(name, self.subdir, self.subproject, kwargs, backend=self.backend) self.add_target(tg.name, tg) return tg @@ -1755,7 +1776,7 @@ This will become a hard error in the future.''', location=self.current_node) if isinstance(i, ExternalProgram) and not i.found(): raise InterpreterException(f'Tried to use non-existing executable {i.name!r}') if isinstance(all_args[0], str): - all_args[0] = self.func_find_program(node, all_args[0], {}) + all_args[0] = self.find_program_impl([all_args[0]]) name = args[0] tg = build.RunTarget(name, all_args, kwargs['depends'], self.subdir, self.subproject, kwargs['env']) self.add_target(name, tg) @@ -1833,7 +1854,7 @@ This will become a hard error in the future.''', location=self.current_node) name = name.replace(':', '_') exe = args[1] if isinstance(exe, mesonlib.File): - exe = self.func_find_program(node, args[1], {}) + exe = self.find_program_impl([exe]) env = self.unpack_env_kwarg(kwargs) @@ -1930,10 +1951,19 @@ This will become a hard error in the future.''', location=self.current_node) return d - @FeatureNewKwargs('subdir', '0.44.0', ['if_found']) - @permittedKwargs({'if_found'}) @typed_pos_args('subdir', str) - def func_subdir(self, node: mparser.BaseNode, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> None: + @typed_kwargs( + 'subdir', + KwargInfo( + 'if_found', + ContainerTypeInfo(list, object), + validator=lambda a: 'Objects must have a found() method' if not all(hasattr(x, 'found') for x in a) else None, + since='0.44.0', + default=[], + listify=True, + ), + ) + def func_subdir(self, node: mparser.BaseNode, args: T.Tuple[str], kwargs: 'kwargs.Subdir') -> None: mesonlib.check_direntry_issues(args) if '..' in args[0]: raise InvalidArguments('Subdir contains ..') @@ -1941,11 +1971,10 @@ This will become a hard error in the future.''', location=self.current_node) raise InvalidArguments('Must not go into subprojects dir with subdir(), use subproject() instead.') if self.subdir == '' and args[0].startswith('meson-'): raise InvalidArguments('The "meson-" prefix is reserved and cannot be used for top-level subdir().') - for i in mesonlib.extract_as_list(kwargs, 'if_found'): - if not hasattr(i, 'found'): - raise InterpreterException('Object used in if_found does not have a found method.') + for i in kwargs['if_found']: if not i.found(): return + prev_subdir = self.subdir subdir = os.path.join(prev_subdir, args[0]) if os.path.isabs(subdir): @@ -2345,49 +2374,43 @@ This will become a hard error in the future.''', location=self.current_node) i = build.IncludeDirs(self.subdir, incdir_strings, is_system) return i - @permittedKwargs({'exe_wrapper', 'gdb', 'timeout_multiplier', 'env', 'is_default', - 'exclude_suites'}) @typed_pos_args('add_test_setup', str) - def func_add_test_setup(self, node: mparser.BaseNode, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> None: + @typed_kwargs( + 'add_test_setup', + KwargInfo('exe_wrapper', ContainerTypeInfo(list, (str, ExternalProgram)), listify=True, default=[]), + KwargInfo('gdb', bool, default=False), + KwargInfo('timeout_multiplier', int, default=1), + KwargInfo('exclude_suites', ContainerTypeInfo(list, str), listify=True, default=[], since='0.57.0'), + KwargInfo('is_default', bool, default=False, since='0.49.0'), + ENV_KW, + ) + def func_add_test_setup(self, node: mparser.BaseNode, args: T.Tuple[str], kwargs: 'kwargs.AddTestSetup') -> None: setup_name = args[0] if re.fullmatch('([_a-zA-Z][_0-9a-zA-Z]*:)?[_a-zA-Z][_0-9a-zA-Z]*', setup_name) is None: raise InterpreterException('Setup name may only contain alphanumeric characters.') if ":" not in setup_name: - setup_name = (self.subproject if self.subproject else self.build.project_name) + ":" + setup_name - try: - inp = extract_as_list(kwargs, 'exe_wrapper') - exe_wrapper = [] - for i in inp: - if isinstance(i, str): - exe_wrapper.append(i) - elif isinstance(i, ExternalProgram): - if not i.found(): - raise InterpreterException('Tried to use non-found executable.') - exe_wrapper += i.get_command() - else: - raise InterpreterException('Exe wrapper can only contain strings or external binaries.') - except KeyError: - exe_wrapper = None - gdb = kwargs.get('gdb', False) - if not isinstance(gdb, bool): - raise InterpreterException('Gdb option must be a boolean') - timeout_multiplier = kwargs.get('timeout_multiplier', 1) - if not isinstance(timeout_multiplier, int): - raise InterpreterException('Timeout multiplier must be a number.') + setup_name = f'{(self.subproject if self.subproject else self.build.project_name)}:{setup_name}' + + exe_wrapper: T.List[str] = [] + for i in kwargs['exe_wrapper']: + if isinstance(i, str): + exe_wrapper.append(i) + else: + if not i.found(): + raise InterpreterException('Tried to use non-found executable.') + exe_wrapper += i.get_command() + + timeout_multiplier = kwargs['timeout_multiplier'] if timeout_multiplier <= 0: FeatureNew('add_test_setup() timeout_multiplier <= 0', '0.57.0').use(self.subproject) - is_default = kwargs.get('is_default', False) - if not isinstance(is_default, bool): - raise InterpreterException('is_default option must be a boolean') - if is_default: + + if kwargs['is_default']: if self.build.test_setup_default_name is not None: raise InterpreterException(f'{self.build.test_setup_default_name!r} is already set as default. ' 'is_default can be set to true only once') self.build.test_setup_default_name = setup_name - exclude_suites = mesonlib.stringlistify(kwargs.get('exclude_suites', [])) - env = self.unpack_env_kwarg(kwargs) - self.build.test_setups[setup_name] = build.TestSetup(exe_wrapper, gdb, timeout_multiplier, env, - exclude_suites) + self.build.test_setups[setup_name] = build.TestSetup(exe_wrapper, kwargs['gdb'], timeout_multiplier, kwargs['env'], + kwargs['exclude_suites']) @typed_pos_args('add_global_arguments', varargs=str) @typed_kwargs('add_global_arguments', NATIVE_KW, LANGUAGE_KW) @@ -2573,7 +2596,7 @@ Try setting b_lundef to false instead.'''.format(self.coredata.options[OptionKey results.append(s) elif isinstance(s, (build.GeneratedList, build.BuildTarget, build.CustomTargetIndex, build.CustomTarget, - build.GeneratedList)): + build.ExtractedObjects)): results.append(s) else: raise InterpreterException(f'Source item is {s!r} instead of ' diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index ccaa1c7..b3dbe55 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -139,7 +139,8 @@ class FeatureOptionHolder(ObjectHolder[coredata.UserFeatureOption]): assert isinstance(error_message, str) if self.value == 'enabled': prefix = f'Feature {self.held_object.name} cannot be enabled' - prefix = prefix + ': ' if error_message else '' + if error_message: + prefix += ': ' raise InterpreterException(prefix + error_message) return self.as_disabled() diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index 2229984..4f8cf06 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -6,7 +6,7 @@ import typing as T -from typing_extensions import TypedDict, Literal +from typing_extensions import TypedDict, Literal, Protocol from .. import build from .. import coredata @@ -186,3 +186,49 @@ class CustomTarget(TypedDict): install_tag: T.List[T.Union[str, bool]] output: T.List[str] override_options: T.Dict[OptionKey, str] + +class AddTestSetup(TypedDict): + + exe_wrapper: T.List[T.Union[str, ExternalProgram]] + gdb: bool + timeout_multiplier: int + is_default: bool + exclude_suites: T.List[str] + env: build.EnvironmentVariables + + +class Project(TypedDict): + + version: T.Optional[FileOrString] + meson_version: T.Optional[str] + default_options: T.List[str] + license: T.List[str] + subproject_dir: str + + +class _FoundProto(Protocol): + + """Protocol for subdir arguments. + + This allows us to define any objec that has a found(self) -> bool method + """ + + def found(self) -> bool: ... + + +class Subdir(TypedDict): + + if_found: T.List[_FoundProto] + + +class Summary(TypedDict): + + section: str + bool_yn: bool + list_sep: T.Optional[str] + + +class FindProgram(ExtractRequired, ExtractSearchDirs): + + native: MachineChoice + version: T.List[str] diff --git a/mesonbuild/interpreter/mesonmain.py b/mesonbuild/interpreter/mesonmain.py index adc342f..e973c5d 100644 --- a/mesonbuild/interpreter/mesonmain.py +++ b/mesonbuild/interpreter/mesonmain.py @@ -86,7 +86,7 @@ class MesonMain(MesonInterpreterObject): largs.append(prog) largs.extend(args) return self.interpreter.backend.get_executable_serialisation(largs) - found = self.interpreter.func_find_program({}, prog, {}) + found = self.interpreter.find_program_impl([prog]) largs.append(found) largs.extend(args) es = self.interpreter.backend.get_executable_serialisation(largs) diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index 9e443ae..5391e9f 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -129,6 +129,8 @@ REQUIRED_KW: KwargInfo[T.Union[bool, UserFeatureOption]] = KwargInfo( # TODO: extract_required_kwarg could be converted to a convertor ) +DISABLER_KW: KwargInfo[bool] = KwargInfo('disabler', bool, default=False) + def _env_validator(value: T.Union[EnvironmentVariables, T.List['TYPE_var'], T.Dict[str, 'TYPE_var'], str, None]) -> T.Optional[str]: def _splitter(v: str) -> T.Optional[str]: split = v.split('=', 1) @@ -285,3 +287,12 @@ INCLUDE_DIRECTORIES: KwargInfo[T.List[T.Union[str, IncludeDirs]]] = KwargInfo( listify=True, default=[], ) + +# for cases like default_options and override_options +DEFAULT_OPTIONS: KwargInfo[T.List[str]] = KwargInfo( + 'default_options', + ContainerTypeInfo(list, (str, IncludeDirs)), + listify=True, + default=[], + validator=_env_validator, +) diff --git a/mesonbuild/mesonlib/universal.py b/mesonbuild/mesonlib/universal.py index 4655810..48ad652 100644 --- a/mesonbuild/mesonlib/universal.py +++ b/mesonbuild/mesonlib/universal.py @@ -2125,7 +2125,7 @@ class OptionKey: return out def __repr__(self) -> str: - return f'OptionKey({repr(self.name)}, {repr(self.subproject)}, {repr(self.machine)}, {repr(self.lang)})' + return f'OptionKey({self.name!r}, {self.subproject!r}, {self.machine!r}, {self.lang!r}, {self.module!r}, {self.type!r})' @classmethod def from_string(cls, raw: str) -> 'OptionKey': diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py index 7d0da13..cb87faf 100644 --- a/mesonbuild/minstall.py +++ b/mesonbuild/minstall.py @@ -18,6 +18,7 @@ import argparse import errno import os import pickle +import platform import shlex import shutil import subprocess @@ -251,6 +252,9 @@ def apply_ldconfig(dm: DirMaker) -> None: # If we don't have ldconfig, failure is ignored quietly. return + if 'bsd' in platform.system().lower(): + return + # Try to update ld cache, it could fail if we don't have permission. proc, out, err = Popen_safe(['ldconfig', '-v']) if proc.returncode == 0: diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index 5798397..f7ce1a0 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -33,7 +33,7 @@ from ..build import CustomTarget, CustomTargetIndex, GeneratedList, InvalidArgum from ..dependencies import Dependency, PkgConfigDependency, InternalDependency from ..interpreter.type_checking import DEPEND_FILES_KW, INSTALL_KW, NoneType, in_set_validator from ..interpreterbase import noPosargs, noKwargs, permittedKwargs, FeatureNew, FeatureDeprecatedKwargs -from ..interpreterbase import typed_kwargs, KwargInfo, ContainerTypeInfo +from ..interpreterbase import typed_kwargs, KwargInfo, ContainerTypeInfo, FeatureDeprecated from ..interpreterbase.decorators import typed_pos_args from ..mesonlib import ( MachineChoice, MesonException, OrderedSet, Popen_safe, join_args, diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py index f874c58..9360e4a 100644 --- a/mesonbuild/modules/qt.py +++ b/mesonbuild/modules/qt.py @@ -170,7 +170,7 @@ class QtBaseModule(ExtensionModule): if qt.found(): # Get all tools and then make sure that they are the right version self.compilers_detect(state, qt) - if version_compare(qt.version, '>=5.14.0'): + if version_compare(qt.version, '>=5.15.0'): self._moc_supports_depfiles = True else: mlog.warning('moc dependencies will not work properly until you move to Qt >= 5.15', fatal=False) diff --git a/mesonbuild/modules/unstable_rust.py b/mesonbuild/modules/unstable_rust.py index 998dbfd..d0d9ca5 100644 --- a/mesonbuild/modules/unstable_rust.py +++ b/mesonbuild/modules/unstable_rust.py @@ -17,7 +17,7 @@ import typing as T from . import ExtensionModule, ModuleReturnValue from .. import mlog -from ..build import BothLibraries, BuildTarget, CustomTargetIndex, Executable, GeneratedList, IncludeDirs, CustomTarget +from ..build import BothLibraries, BuildTarget, CustomTargetIndex, Executable, ExtractedObjects, GeneratedList, IncludeDirs, CustomTarget from ..dependencies import Dependency, ExternalLibrary from ..interpreter.interpreter import TEST_KWARGS from ..interpreterbase import ContainerTypeInfo, InterpreterException, KwargInfo, FeatureNew, typed_kwargs, typed_pos_args, noPosargs @@ -27,7 +27,7 @@ if T.TYPE_CHECKING: from . import ModuleState from ..interpreter import Interpreter from ..interpreter import kwargs as _kwargs - from ..interpreter.interpreter import SourceInputs + from ..interpreter.interpreter import SourceInputs, SourceOutputs from ..programs import ExternalProgram from typing_extensions import TypedDict @@ -168,7 +168,7 @@ class RustModule(ExtensionModule): KwargInfo('include_directories', ContainerTypeInfo(list, IncludeDirs), default=[], listify=True), KwargInfo( 'input', - ContainerTypeInfo(list, (File, GeneratedList, BuildTarget, BothLibraries, CustomTargetIndex, CustomTarget, str), allow_empty=False), + ContainerTypeInfo(list, (File, GeneratedList, BuildTarget, BothLibraries, ExtractedObjects, CustomTargetIndex, CustomTarget, str), allow_empty=False), default=[], listify=True, required=True, @@ -184,7 +184,7 @@ class RustModule(ExtensionModule): header, *_deps = self.interpreter.source_strings_to_files(kwargs['input']) # Split File and Target dependencies to add pass to CustomTarget - depends: T.List[T.Union[GeneratedList, BuildTarget, CustomTargetIndex, CustomTarget]] = [] + depends: T.List['SourceOutputs'] = [] depend_files: T.List[File] = [] for d in _deps: if isinstance(d, File): @@ -203,6 +203,8 @@ class RustModule(ExtensionModule): name: str if isinstance(header, File): name = header.fname + elif isinstance(header, (BuildTarget, BothLibraries, ExtractedObjects)): + raise InterpreterException('bindgen source file must be a C header, not an object or build target') else: name = header.get_outputs()[0] diff --git a/test cases/failing/106 feature require.bis/meson.build b/test cases/failing/106 feature require.bis/meson.build new file mode 100644 index 0000000..08c099c --- /dev/null +++ b/test cases/failing/106 feature require.bis/meson.build @@ -0,0 +1,2 @@ +project('no fallback', 'c') +foo = get_option('reqfeature').require(false) diff --git a/test cases/failing/106 feature require.bis/meson_options.txt b/test cases/failing/106 feature require.bis/meson_options.txt new file mode 100644 index 0000000..5910a87 --- /dev/null +++ b/test cases/failing/106 feature require.bis/meson_options.txt @@ -0,0 +1 @@ +option('reqfeature', type : 'feature', value : 'enabled', description : 'A required feature') diff --git a/test cases/failing/106 feature require.bis/test.json b/test cases/failing/106 feature require.bis/test.json new file mode 100644 index 0000000..2583990 --- /dev/null +++ b/test cases/failing/106 feature require.bis/test.json @@ -0,0 +1,8 @@ +{ + "stdout": [ + { + "match": "re", + "line": ".*/meson\\.build:2:0: ERROR: Feature reqfeature cannot be enabled" + } + ] +} diff --git a/test cases/failing/115 nonsensical bindgen/meson.build b/test cases/failing/115 nonsensical bindgen/meson.build new file mode 100644 index 0000000..6995f67 --- /dev/null +++ b/test cases/failing/115 nonsensical bindgen/meson.build @@ -0,0 +1,20 @@ +# SPDX-license-identifer: Apache-2.0 +# Copyright © 2021 Intel Corporation + +project('rustmod bindgen', 'c') + +if not add_languages('rust', required: false) + error('MESON_SKIP_TEST test requires rust compiler') +endif + +prog_bindgen = find_program('bindgen', required : false) +if not prog_bindgen.found() + error('MESON_SKIP_TEST bindgen not found') +endif + +c_lib = static_library('clib', 'src/source.c') + +import('unstable-rust').bindgen( + input : c_lib, + output : 'header.rs', +) diff --git a/test cases/failing/115 nonsensical bindgen/src/header.h b/test cases/failing/115 nonsensical bindgen/src/header.h new file mode 100644 index 0000000..750621f --- /dev/null +++ b/test cases/failing/115 nonsensical bindgen/src/header.h @@ -0,0 +1,8 @@ +// SPDX-license-identifer: Apache-2.0 +// Copyright © 2021 Intel Corporation + +#pragma once + +#include <stdint.h> + +int32_t add(const int32_t, const int32_t); diff --git a/test cases/failing/115 nonsensical bindgen/src/source.c b/test cases/failing/115 nonsensical bindgen/src/source.c new file mode 100644 index 0000000..d652d28 --- /dev/null +++ b/test cases/failing/115 nonsensical bindgen/src/source.c @@ -0,0 +1,8 @@ +// SPDX-license-identifer: Apache-2.0 +// Copyright © 2021 Intel Corporation + +#include "header.h" + +int32_t add(const int32_t first, const int32_t second) { + return first + second; +} diff --git a/test cases/failing/115 nonsensical bindgen/test.json b/test cases/failing/115 nonsensical bindgen/test.json new file mode 100644 index 0000000..d9249b2 --- /dev/null +++ b/test cases/failing/115 nonsensical bindgen/test.json @@ -0,0 +1,8 @@ +{ + "stdout": [ + { + "line": "test cases/failing/115 nonsensical bindgen/meson.build:17:24: ERROR: bindgen source file must be a C header, not an object or build target" + } + ] +} + diff --git a/test cases/failing/75 link with shared module on osx/test.json b/test cases/failing/75 link with shared module on osx/test.json index 7db17d8..81ee2ac 100644 --- a/test cases/failing/75 link with shared module on osx/test.json +++ b/test cases/failing/75 link with shared module on osx/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/75 link with shared module on osx/meson.build:8:0: ERROR: target links against shared modules. This is not permitted on OSX" + "line": "test cases/failing/75 link with shared module on osx/meson.build:8:0: ERROR: target prog links against shared module mymodule. This is not permitted on OSX" } ] } diff --git a/test cases/unit/1 soname/main.c b/test cases/unit/1 soname/main.c new file mode 100644 index 0000000..f5ccbb9 --- /dev/null +++ b/test cases/unit/1 soname/main.c @@ -0,0 +1,5 @@ +int versioned_func (void); + +int main (void) { + return versioned_func(); +} diff --git a/test cases/unit/1 soname/meson.build b/test cases/unit/1 soname/meson.build index 950dadc..44b003a 100644 --- a/test cases/unit/1 soname/meson.build +++ b/test cases/unit/1 soname/meson.build @@ -20,3 +20,16 @@ shared_library('settosame', 'versioned.c', install : true, soversion : '7.8.9', version : '7.8.9') + +shared_module('some_module', 'versioned.c', + install: true) + +module1 = shared_module('linked_module1', 'versioned.c', + install: true) + +module2 = shared_module('linked_module2', 'versioned.c', + install: true) +module2_dep = declare_dependency(link_with: module2) + +executable('main1', 'main.c', link_with: module1) +executable('main2', 'main.c', dependencies: module2_dep) diff --git a/test cases/unit/17 prebuilt shared/meson.build b/test cases/unit/17 prebuilt shared/meson.build index 9a4eca0..7badcb7 100644 --- a/test cases/unit/17 prebuilt shared/meson.build +++ b/test cases/unit/17 prebuilt shared/meson.build @@ -1,7 +1,12 @@ project('prebuilt shared library', 'c') +search_dir = get_option('search_dir') +if search_dir == 'auto' + search_dir = meson.current_source_dir() +endif + cc = meson.get_compiler('c') -shlib = cc.find_library('alexandria', dirs : meson.current_source_dir()) +shlib = cc.find_library('alexandria', dirs : search_dir) exe = executable('patron', 'patron.c', dependencies : shlib) test('visitation', exe) @@ -11,3 +16,23 @@ d = declare_dependency(dependencies : shlib) exe2 = executable('another_visitor', 'another_visitor.c', dependencies : d) test('another', exe2) + +stlib = static_library( + 'rejected', + 'rejected.c', + dependencies : shlib, +) + +rejected = executable( + 'rejected', + 'rejected_main.c', + link_with : stlib, +) +test('rejected', rejected) + +rejected_whole = executable( + 'rejected_whole', + 'rejected_main.c', + link_whole : stlib, +) +test('rejected (whole archive)', rejected_whole) diff --git a/test cases/unit/17 prebuilt shared/meson_options.txt b/test cases/unit/17 prebuilt shared/meson_options.txt new file mode 100644 index 0000000..7876a6f --- /dev/null +++ b/test cases/unit/17 prebuilt shared/meson_options.txt @@ -0,0 +1 @@ +option('search_dir', type : 'string', value : 'auto') diff --git a/test cases/unit/17 prebuilt shared/rejected.c b/test cases/unit/17 prebuilt shared/rejected.c new file mode 100644 index 0000000..9d7ac94 --- /dev/null +++ b/test cases/unit/17 prebuilt shared/rejected.c @@ -0,0 +1,8 @@ +#include "rejected.h" + +void say(void) { + printf("You are standing outside the Great Library of Alexandria.\n"); + printf("You decide to go inside.\n\n"); + alexandria_visit(); + printf("The librarian tells you it's time to leave\n"); +} diff --git a/test cases/unit/17 prebuilt shared/rejected.h b/test cases/unit/17 prebuilt shared/rejected.h new file mode 100644 index 0000000..b9ccf31 --- /dev/null +++ b/test cases/unit/17 prebuilt shared/rejected.h @@ -0,0 +1,6 @@ +#include <stdio.h> +#include <alexandria.h> + +#pragma once + +void say(void); diff --git a/test cases/unit/17 prebuilt shared/rejected_main.c b/test cases/unit/17 prebuilt shared/rejected_main.c new file mode 100644 index 0000000..4d35061 --- /dev/null +++ b/test cases/unit/17 prebuilt shared/rejected_main.c @@ -0,0 +1,6 @@ +#include "rejected.h" + +int main(void) { + say(); + return 0; +} diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index ec0b393..30c0572 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -1499,19 +1499,45 @@ class AllPlatformTests(BasePlatformTests): else: shlibfile = os.path.join(tdir, 'libalexandria.' + shared_suffix) self.build_shared_lib(cc, source, objectfile, shlibfile, impfile) + + if is_windows(): + def cleanup() -> None: + """Clean up all the garbage MSVC writes in the source tree.""" + + for fname in glob(os.path.join(tdir, 'alexandria.*')): + if os.path.splitext(fname)[1] not in {'.c', '.h'}: + os.unlink(fname) + self.addCleanup(cleanup) + else: + self.addCleanup(os.unlink, shlibfile) + # Run the test - try: - self.init(tdir) + self.init(tdir) + self.build() + self.run_tests() + + def test_prebuilt_shared_lib_rpath(self) -> None: + (cc, _, object_suffix, shared_suffix) = self.detect_prebuild_env() + tdir = os.path.join(self.unit_test_dir, '17 prebuilt shared') + with tempfile.TemporaryDirectory() as d: + source = os.path.join(tdir, 'alexandria.c') + objectfile = os.path.join(d, 'alexandria.' + object_suffix) + impfile = os.path.join(d, 'alexandria.lib') + if cc.get_argument_syntax() == 'msvc': + shlibfile = os.path.join(d, 'alexandria.' + shared_suffix) + elif is_cygwin(): + shlibfile = os.path.join(d, 'cygalexandria.' + shared_suffix) + else: + shlibfile = os.path.join(d, 'libalexandria.' + shared_suffix) + # Ensure MSVC extra files end up in the directory that gets deleted + # at the end + with chdir(d): + self.build_shared_lib(cc, source, objectfile, shlibfile, impfile) + + # Run the test + self.init(tdir, extra_args=[f'-Dsearch_dir={d}']) self.build() self.run_tests() - finally: - os.unlink(shlibfile) - if is_windows(): - # Clean up all the garbage MSVC writes in the - # source tree. - for fname in glob(os.path.join(tdir, 'alexandria.*')): - if os.path.splitext(fname)[1] not in ['.c', '.h']: - os.unlink(fname) @skipIfNoPkgconfig def test_pkgconfig_static(self): @@ -1920,8 +1946,10 @@ class AllPlatformTests(BasePlatformTests): """ tdir = os.path.join(self.unit_test_dir, '30 shared_mod linking') out = self.init(tdir) - msg = ('WARNING: target links against shared modules. This is not ' - 'recommended as it is not supported on some platforms') + msg = ('''DEPRECATION: target prog links against shared module mymod, which is incorrect. + This will be an error in the future, so please use shared_library() for mymod instead. + If shared_module() was used for mymod because it has references to undefined symbols, + use shared_libary() with `override_options: ['b_lundef=false']` instead.''') self.assertIn(msg, out) def test_mixed_language_linker_check(self): @@ -3540,6 +3568,12 @@ class AllPlatformTests(BasePlatformTests): self.build() self.run_tests() + def test_extract_objects_custom_target_no_warning(self): + testdir = os.path.join(self.common_test_dir, '22 object extraction') + + out = self.init(testdir) + self.assertNotRegex(out, "WARNING:.*can't be converted to File object") + def test_multi_output_custom_target_no_warning(self): testdir = os.path.join(self.common_test_dir, '228 custom_target source') diff --git a/unittests/internaltests.py b/unittests/internaltests.py index 31271b7..c4fd0a6 100644 --- a/unittests/internaltests.py +++ b/unittests/internaltests.py @@ -13,6 +13,7 @@ # limitations under the License. from configparser import ConfigParser +from mesonbuild.mesonlib.universal import OptionType from pathlib import Path from unittest import mock import contextlib @@ -1574,3 +1575,21 @@ class InternalTests(unittest.TestCase): self.assertFalse(coredata.major_versions_differ('0.60.0', '0.60.1')) self.assertFalse(coredata.major_versions_differ('0.59.99', '0.59.99')) self.assertFalse(coredata.major_versions_differ('0.60.0.rc1', '0.60.0.rc2')) + + def test_option_key_from_string(self) -> None: + cases = [ + ('c_args', OptionKey('args', lang='c', _type=OptionType.COMPILER)), + ('build.cpp_args', OptionKey('args', machine=MachineChoice.BUILD, lang='cpp', _type=OptionType.COMPILER)), + ('prefix', OptionKey('prefix', _type=OptionType.BUILTIN)), + ('made_up', OptionKey('made_up', _type=OptionType.PROJECT)), + + # TODO: the from_String method should be splitting the prefix off of + # these, as we have the type already, but it doesn't. For now have a + # test so that we don't change the behavior un-intentionally + ('b_lto', OptionKey('b_lto', _type=OptionType.BASE)), + ('backend_startup_project', OptionKey('backend_startup_project', _type=OptionType.BACKEND)), + ] + + for raw, expected in cases: + with self.subTest(raw): + self.assertEqual(OptionKey.from_string(raw), expected) diff --git a/unittests/linuxliketests.py b/unittests/linuxliketests.py index 0f99f01..8ff0f8e 100644 --- a/unittests/linuxliketests.py +++ b/unittests/linuxliketests.py @@ -438,6 +438,24 @@ class LinuxlikeTests(BasePlatformTests): self.assertEqual(get_soname(bothset), 'libbothset.so.1.2.3') self.assertEqual(len(self.glob_sofiles_without_privdir(bothset[:-3] + '*')), 3) + # A shared_module that is not linked to anything + module = os.path.join(libpath, 'libsome_module.so') + self.assertPathExists(module) + self.assertFalse(os.path.islink(module)) + self.assertEqual(get_soname(module), None) + + # A shared_module that is not linked to an executable with link_with: + module = os.path.join(libpath, 'liblinked_module1.so') + self.assertPathExists(module) + self.assertFalse(os.path.islink(module)) + self.assertEqual(get_soname(module), 'liblinked_module1.so') + + # A shared_module that is not linked to an executable with dependencies: + module = os.path.join(libpath, 'liblinked_module2.so') + self.assertPathExists(module) + self.assertFalse(os.path.islink(module)) + self.assertEqual(get_soname(module), 'liblinked_module2.so') + def test_soname(self): self._test_soname_impl(self.builddir, False) |