diff options
33 files changed, 501 insertions, 60 deletions
diff --git a/docs/markdown/Builtin-options.md b/docs/markdown/Builtin-options.md index 957ce4e..d25d7ab 100644 --- a/docs/markdown/Builtin-options.md +++ b/docs/markdown/Builtin-options.md @@ -132,7 +132,7 @@ compiler being used: | c_winlibs | see below | free-form comma-separated list | Standard Windows libs to link against | | cpp_args | | free-form comma-separated list | C++ compile arguments to use | | cpp_link_args| | free-form comma-separated list | C++ link arguments to use | -| cpp_std | none | none, c++98, c++03, c++11, c++14, c++17, <br/>c++1z, gnu++03, gnu++11, gnu++14, gnu++17, gnu++1z | C++ language standard to use | +| cpp_std | none | none, c++98, c++03, c++11, c++14, c++17, <br/>c++1z, gnu++03, gnu++11, gnu++14, gnu++17, gnu++1z, <br/> vc++14, vc++17, vc++latest | C++ language standard to use | | cpp_debugstl | false | true, false | C++ STL debug mode | | cpp_eh | sc | none, a, s, sc | C++ exception handling type | | cpp_winlibs | see below | free-form comma-separated list | Standard Windows libs to link against | diff --git a/docs/markdown/FAQ.md b/docs/markdown/FAQ.md index ff93216..0208c1a 100644 --- a/docs/markdown/FAQ.md +++ b/docs/markdown/FAQ.md @@ -331,3 +331,37 @@ that could fullfill these requirements: Out of these we have chosen Python because it is the best fit for our needs. + +## I have proprietary compiler toolchain X that does not work with Meson, how can I make it work? + +Meson needs to know several details about each compiler in order to +compile code with it. These include things such as which compiler +flags to use for each option and how to detect the compiler from its +output. This information can not be input via a configuration file, +instead it requires changes to Meson's source code that need to be +submitted to Meson master repository. In theory you can run your own +forked version with custom patches, but that's not good use of your +time. Please submit the code upstream so everyone can use the +toolchain. + +The steps for adding a new compiler for an existing language are +roughly the following. For simplicity we're going to assume a C +compiler. + +- Create a new class with a proper name in + `mesonbuild/compilers/c.py`. Look at the methods that other + compilers for the same language have and duplicate what they do. + +- If the compiler can only be used for cross compilation, make sure to + flag it as such (see existing compiler classes for examples). + +- Add detection logic to `mesonbuild/environment.py`, look for a + method called `detect_c_compiler`. + +- Run the test suite and fix issues until the tests pass. + +- Submit a pull request, add the result of the test suite to your MR + (linking an existing page is fine). + +- If the compiler is freely available, consider adding it to the CI + system. diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 056612d..d86d825 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -518,6 +518,8 @@ be passed to [shared and static libraries](#library). depends on such as a symbol visibility map. The purpose is to automatically trigger a re-link (but not a re-compile) of the target when this file changes. +- `link_language` since 0.51.0 makes the linker for this target + be for the specified language. This is helpful for multi-language targets. - `link_whole` links all contents of the given static libraries whether they are used by not, equivalent to the `-Wl,--whole-archive` argument flag of GCC, available since 0.40.0. @@ -568,7 +570,7 @@ be passed to [shared and static libraries](#library). the keyword argument for the default behaviour. - `override_options` takes an array of strings in the same format as `project`'s `default_options` overriding the values of these options - for this target only, since 0.40.0 + for this target only, since 0.40.0. - `gnu_symbol_visibility` specifies how symbols should be exported, see e.g [the GCC Wiki](https://gcc.gnu.org/wiki/Visibility) for more information. This value can either be an empty string or one of diff --git a/docs/markdown/snippets/link_language.md b/docs/markdown/snippets/link_language.md new file mode 100644 index 0000000..28ebe8b --- /dev/null +++ b/docs/markdown/snippets/link_language.md @@ -0,0 +1,10 @@ +## New target keyword argument: `link_language` +There may be situations for which the user wishes to manually specify the linking language. +For example, a C++ target may link C, Fortran, etc. and perhaps the automatic detection in Meson does not pick the desired compiler. +The user can manually choose the linker by language per-target like this example of a target where one wishes to link with the Fortran compiler: +```meson +executable(..., link_language : 'fortran') +``` + +A specific case this option fixes is where for example the main program is Fortran that calls C and/or C++ code. +The automatic language detection of Meson prioritizes C/C++, and so an compile-time error results like `undefined reference to main`, because the linker is C or C++ instead of Fortran, which is fixed by this per-target override. diff --git a/docs/markdown/snippets/linkcustom.md b/docs/markdown/snippets/linkcustom.md index d6ee801..0cf45ad 100644 --- a/docs/markdown/snippets/linkcustom.md +++ b/docs/markdown/snippets/linkcustom.md @@ -1,6 +1,6 @@ ## Can link against custom targets -The output of `custom_target` can be used in `link_with` and +The output of `custom_target` and `custom_target[i]` can be used in `link_with` and `link_whole` keyword arguments. This is useful for integrating custom code generator steps, but note that there are many limitations: @@ -10,7 +10,8 @@ code generator steps, but note that there are many limitations: - The user is responsible for ensuring that the code produced by different toolchains are compatible. - - The custom target can only have one output file. + - `custom_target` may only be used when it has a single output file. + Use `custom_target[i]` when dealing with multiple output files. - The output file must have the correct file name extension. diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 5ff9b55..d0b4bb5 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -171,6 +171,8 @@ class Backend: mlog.warning('custom_target {!r} has more than one output! ' 'Using the first one.'.format(t.name)) filename = t.get_outputs()[0] + elif isinstance(t, build.CustomTargetIndex): + filename = t.get_outputs()[0] else: assert(isinstance(t, build.BuildTarget)) filename = t.get_filename() @@ -214,7 +216,7 @@ class Backend: return os.path.join(self.get_target_dir(target), link_lib) elif isinstance(target, build.StaticLibrary): return os.path.join(self.get_target_dir(target), target.get_filename()) - elif isinstance(target, build.CustomTarget): + elif isinstance(target, (build.CustomTarget, build.CustomTargetIndex)): if not target.is_linkable_target(): raise MesonException('Tried to link against custom target "%s", which is not linkable.' % target.name) return os.path.join(self.get_target_dir(target), target.get_filename()) diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 3950a2e..d25798e 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -249,9 +249,15 @@ class Vs2010Backend(backends.Backend): all_deps[d.get_id()] = d elif isinstance(target, build.BuildTarget): for ldep in target.link_targets: - all_deps[ldep.get_id()] = ldep + if isinstance(ldep, build.CustomTargetIndex): + all_deps[ldep.get_id()] = ldep.target + else: + all_deps[ldep.get_id()] = ldep for ldep in target.link_whole_targets: - all_deps[ldep.get_id()] = ldep + if isinstance(ldep, build.CustomTargetIndex): + all_deps[ldep.get_id()] = ldep.target + else: + all_deps[ldep.get_id()] = ldep for obj_id, objdep in self.get_obj_target_deps(target.objects): all_deps[obj_id] = objdep for gendep in target.get_generated_sources(): @@ -1111,7 +1117,11 @@ class Vs2010Backend(backends.Backend): # Add more libraries to be linked if needed for t in target.get_dependencies(): - lobj = self.build.targets[t.get_id()] + if isinstance(t, build.CustomTargetIndex): + # We don't need the actual project here, just the library name + lobj = t + else: + lobj = self.build.targets[t.get_id()] linkname = os.path.join(down, self.get_target_filename_for_linking(lobj)) if t in target.link_whole_targets: # /WHOLEARCHIVE:foo must go into AdditionalOptions diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 5248d97..603e0d0 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import List import copy, os, re from collections import OrderedDict import itertools, pathlib @@ -28,7 +29,7 @@ from .mesonlib import ( get_filenames_templates_dict, substitute_values, for_windows, for_darwin, for_cygwin, for_android, has_path_sep ) -from .compilers import is_object, clink_langs, sort_clink, lang_suffixes, get_macos_dylib_install_name +from .compilers import Compiler, is_object, clink_langs, sort_clink, lang_suffixes, get_macos_dylib_install_name from .interpreterbase import FeatureNew pch_kwargs = set(['c_pch', 'cpp_pch']) @@ -88,7 +89,7 @@ known_build_target_kwargs = ( rust_kwargs | cs_kwargs) -known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'pie'} +known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'link_language', 'pie'} known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions'} known_shmod_kwargs = known_build_target_kwargs known_stlib_kwargs = known_build_target_kwargs | {'pic'} @@ -425,7 +426,7 @@ a hard error in the future.''' % name) self.option_overrides = self.parse_overrides(kwargs) - def parse_overrides(self, kwargs): + def parse_overrides(self, kwargs) -> dict: result = {} overrides = stringlistify(kwargs.get('override_options', [])) for o in overrides: @@ -437,7 +438,7 @@ a hard error in the future.''' % name) result[k] = v return result - def is_linkable_target(self): + def is_linkable_target(self) -> bool: return False class BuildTarget(Target): @@ -450,10 +451,11 @@ class BuildTarget(Target): self.is_unity = unity_opt == 'on' or (unity_opt == 'subprojects' and subproject != '') self.environment = environment self.sources = [] - self.compilers = OrderedDict() + self.compilers = OrderedDict() # type: OrderedDict[str, Compiler] self.objects = [] self.external_deps = [] self.include_dirs = [] + self.link_language = kwargs.get('link_language') self.link_targets = [] self.link_whole_targets = [] self.link_depends = [] @@ -571,15 +573,18 @@ class BuildTarget(Target): else: compilers = self.environment.coredata.compilers + # did user override clink_langs for this target? + link_langs = [self.link_language] if self.link_language else clink_langs + # If this library is linked against another library we need to consider # the languages of those libraries as well. if self.link_targets or self.link_whole_targets: extra = set() for t in itertools.chain(self.link_targets, self.link_whole_targets): - if isinstance(t, CustomTarget): + if isinstance(t, CustomTarget) or isinstance(t, CustomTargetIndex): continue # We can't know anything about these. for name, compiler in t.compilers.items(): - if name in clink_langs: + if name in link_langs: extra.add((name, compiler)) for name, compiler in sorted(extra, key=lambda p: sort_clink(p[0])): self.compilers[name] = compiler @@ -588,7 +593,7 @@ class BuildTarget(Target): # No source files or parent targets, target consists of only object # files of unknown origin. Just add the first clink compiler # that we have and hope that it can link these objects - for lang in clink_langs: + for lang in link_langs: if lang in compilers: self.compilers[lang] = compilers[lang] break @@ -1066,7 +1071,7 @@ You probably should put it in link_with instead.''') def link(self, target): for t in listify(target, unholder=True): - if not isinstance(t, Target): + if not isinstance(t, (Target, CustomTargetIndex)): raise InvalidArguments('{!r} is not a target.'.format(t)) if not t.is_linkable_target(): raise InvalidArguments('Link target {!r} is not linkable.'.format(t)) @@ -1074,13 +1079,13 @@ You probably should put it in link_with instead.''') msg = "Can't link non-PIC static library {!r} into shared library {!r}. ".format(t.name, self.name) msg += "Use the 'pic' option to static_library to build with PIC." raise InvalidArguments(msg) - if not isinstance(t, CustomTarget) and self.is_cross != t.is_cross: + if not isinstance(t, (CustomTarget, CustomTargetIndex)) and self.is_cross != t.is_cross: raise InvalidArguments('Tried to mix cross built and native libraries in target {!r}'.format(self.name)) self.link_targets.append(t) def link_whole(self, target): for t in listify(target, unholder=True): - if isinstance(t, CustomTarget): + if isinstance(t, (CustomTarget, CustomTargetIndex)): if not t.is_linkable_target(): raise InvalidArguments('Custom target {!r} is not linkable.'.format(t)) if not t.get_filename().endswith('.a'): @@ -1091,7 +1096,7 @@ You probably should put it in link_with instead.''') msg = "Can't link non-PIC static library {!r} into shared library {!r}. ".format(t.name, self.name) msg += "Use the 'pic' option to static_library to build with PIC." raise InvalidArguments(msg) - if not isinstance(t, CustomTarget) and self.is_cross != t.is_cross: + if not isinstance(t, (CustomTarget, CustomTargetIndex)) and self.is_cross != t.is_cross: raise InvalidArguments('Tried to mix cross built and native libraries in target {!r}'.format(self.name)) self.link_whole_targets.append(t) @@ -1149,7 +1154,7 @@ You probably should put it in link_with instead.''') def get_aliases(self): return {} - def get_langs_used_by_deps(self): + def get_langs_used_by_deps(self) -> List[str]: ''' Sometimes you want to link to a C++ library that exports C API, which means the linker must link in the C++ stdlib, and we must use a C++ @@ -1159,6 +1164,11 @@ You probably should put it in link_with instead.''') See: https://github.com/mesonbuild/meson/issues/1653 ''' langs = [] + + # User specified link_language of target (for multi-language targets) + if self.link_language: + return [self.link_language] + # Check if any of the external libraries were written in this language for dep in self.external_deps: if dep.language is None: @@ -1168,11 +1178,12 @@ You probably should put it in link_with instead.''') # Check if any of the internal libraries this target links to were # written in this language for link_target in itertools.chain(self.link_targets, self.link_whole_targets): - if isinstance(link_target, CustomTarget): + if isinstance(link_target, (CustomTarget, CustomTargetIndex)): continue for language in link_target.compilers: if language not in langs: langs.append(language) + return langs def get_clink_dynamic_linker_and_stdlibs(self): @@ -2259,6 +2270,26 @@ class CustomTargetIndex: def get_subdir(self): return self.target.get_subdir() + def get_filename(self): + return self.output + + def get_id(self): + return self.target.get_id() + + def get_all_link_deps(self): + return self.target.get_all_link_deps() + + def get_link_deps_mapping(self, prefix, environment): + return self.target.get_link_deps_mapping(prefix, environment) + + def get_link_dep_subdirs(self): + return self.target.get_link_dep_subdirs() + + def is_linkable_target(self): + suf = os.path.splitext(self.output)[-1] + if suf == '.a' or suf == '.dll' or suf == '.lib' or suf == '.so': + return True + class ConfigureFile: def __init__(self, subdir, sourcename, targetname, configuration_data): diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index 7c2253d..2b2c4a0 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -23,7 +23,6 @@ from .c import CCompiler, VisualStudioCCompiler, ClangClCCompiler, IntelClCCompi from .compilers import ( gnu_winlibs, msvc_winlibs, - CompilerType, ClangCompiler, GnuCompiler, ElbrusCompiler, diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index dd54fd0..86ebe05 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -333,7 +333,6 @@ class GnuFortranCompiler(GnuCompiler, FortranCompiler): def language_stdlib_only_link_flags(self): return ['-lgfortran', '-lm'] - class ElbrusFortranCompiler(GnuFortranCompiler, ElbrusCompiler): def __init__(self, exelist, version, compiler_type, is_cross, exe_wrapper=None, defines=None, **kwargs): GnuFortranCompiler.__init__(self, exelist, version, compiler_type, is_cross, exe_wrapper, defines, **kwargs) @@ -427,6 +426,9 @@ class PGIFortranCompiler(PGICompiler, FortranCompiler): FortranCompiler.__init__(self, exelist, version, is_cross, exe_wrapper, **kwags) PGICompiler.__init__(self, compiler_type) + def language_stdlib_only_link_flags(self) -> List[str]: + return ['-lpgf90rtl', '-lpgf90', '-lpgf90_rpm1', '-lpgf902', + '-lpgf90rtl', '-lpgftnrtl', '-lrt'] class FlangFortranCompiler(ClangCompiler, FortranCompiler): def __init__(self, exelist, version, is_cross, exe_wrapper=None, **kwags): diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index c09f799..9664215 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -14,7 +14,6 @@ # This file contains the detection logic for external dependencies. # Custom logic for several other packages are in separate files. -from typing import Dict, Any import copy import functools import os @@ -26,7 +25,7 @@ import textwrap import platform import itertools import ctypes -from typing import List, Tuple +from typing import Any, Dict, List, Tuple from enum import Enum from pathlib import Path, PurePath @@ -2304,7 +2303,7 @@ class ExtraFrameworkDependency(ExternalDependency): return 'framework' -def get_dep_identifier(name, kwargs, want_cross): +def get_dep_identifier(name, kwargs, want_cross: bool) -> Tuple: identifier = (name, want_cross) for key, value in kwargs.items(): # 'version' is irrelevant for caching; the caller must check version matches diff --git a/mesonbuild/dependencies/platform.py b/mesonbuild/dependencies/platform.py index 9863fb1..e913ed4 100644 --- a/mesonbuild/dependencies/platform.py +++ b/mesonbuild/dependencies/platform.py @@ -16,7 +16,7 @@ # platform-specific (generally speaking). from .base import ExternalDependency, DependencyException - +from ..mesonlib import MesonException class AppleFrameworks(ExternalDependency): def __init__(self, env, kwargs): @@ -31,7 +31,16 @@ class AppleFrameworks(ExternalDependency): raise DependencyException('No C-like compilers are available, cannot find the framework') self.is_found = True for f in self.frameworks: - args = self.clib_compiler.find_framework(f, env, []) + try: + args = self.clib_compiler.find_framework(f, env, []) + except MesonException as e: + if 'non-clang' in str(e): + self.is_found = False + self.link_args = [] + self.compile_args = [] + return + raise + if args is not None: # No compile args are needed for system frameworks self.link_args += args diff --git a/mesonbuild/envconfig.py b/mesonbuild/envconfig.py index f4c371f..977d930 100644 --- a/mesonbuild/envconfig.py +++ b/mesonbuild/envconfig.py @@ -255,7 +255,7 @@ class MachineInfo: def libdir_layout_is_win(self) -> bool: return self.is_windows() or self.is_cygwin() -class PerMachineDefaultable(PerMachine[_T]): +class PerMachineDefaultable(PerMachine[typing.Optional[_T]]): """Extends `PerMachine` with the ability to default from `None`s. """ def __init__(self) -> None: @@ -285,7 +285,7 @@ class PerMachineDefaultable(PerMachine[_T]): if self.host == self.build: self.host = None -class MachineInfos(PerMachineDefaultable[typing.Optional[MachineInfo]]): +class MachineInfos(PerMachineDefaultable[MachineInfo]): def matches_build_machine(self, machine: MachineChoice) -> bool: return self.build == self[machine] diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index bf2cce9..462672e 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os, platform, re, sys, shlex, shutil, subprocess +import os, platform, re, sys, shlex, shutil, subprocess, typing from . import coredata from .linkers import ArLinker, ArmarLinker, VisualStudioLinker, DLinker, CcrxLinker @@ -28,6 +28,7 @@ from .envconfig import ( ) from . import compilers from .compilers import ( + Compiler, CompilerType, is_assembly, is_header, @@ -83,6 +84,8 @@ from .compilers import ( build_filename = 'meson.build' +CompilersDict = typing.Dict[str, Compiler] + def detect_gcovr(min_version='3.3', new_rootdir_version='4.2', log=False): gcovr_exe = 'gcovr' try: @@ -150,7 +153,7 @@ def detect_native_windows_arch(): raise EnvironmentException('Unable to detect native OS architecture') return arch -def detect_windows_arch(compilers): +def detect_windows_arch(compilers: CompilersDict) -> str: """ Detecting the 'native' architecture of Windows is not a trivial task. We cannot trust that the architecture that Python is built for is the 'native' @@ -190,7 +193,7 @@ def detect_windows_arch(compilers): return 'x86' return os_arch -def any_compiler_has_define(compilers, define): +def any_compiler_has_define(compilers: CompilersDict, define): for c in compilers.values(): try: if c.has_builtin_define(define): @@ -200,7 +203,7 @@ def any_compiler_has_define(compilers, define): pass return False -def detect_cpu_family(compilers): +def detect_cpu_family(compilers: CompilersDict) -> str: """ Python is inconsistent in its platform module. It returns different values for the same cpu. @@ -262,7 +265,7 @@ def detect_cpu_family(compilers): return trial -def detect_cpu(compilers): +def detect_cpu(compilers: CompilersDict): if mesonlib.is_windows(): trial = detect_windows_arch(compilers) else: @@ -295,7 +298,7 @@ def detect_msys2_arch(): return os.environ['MSYSTEM_CARCH'] return None -def detect_machine_info(compilers = None) -> MachineInfo: +def detect_machine_info(compilers: typing.Optional[CompilersDict] = None) -> MachineInfo: """Detect the machine we're running on If compilers are not provided, we cannot know as much. None out those diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 9bb4a3c..9d1a27d 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2996,6 +2996,7 @@ external dependencies (including libraries) must go to "dependencies".''') self._handle_featurenew_dependencies(name) kwargs['required'] = required and not has_fallback dep = dependencies.find_external_dependency(name, self.environment, kwargs) + kwargs['required'] = required # Only store found-deps in the cache # Never add fallback deps to self.coredata.deps since we diff --git a/mesonbuild/mesonlib.py b/mesonbuild/mesonlib.py index d5e60fd..aaaf144 100644 --- a/mesonbuild/mesonlib.py +++ b/mesonbuild/mesonlib.py @@ -323,22 +323,20 @@ class MachineChoice(OrderedEnum): HOST = 1 TARGET = 2 -_T = typing.TypeVar('_T') - class PerMachine(typing.Generic[_T]): - def __init__(self, build: typing.Optional[_T], host: typing.Optional[_T], target: typing.Optional[_T]): + def __init__(self, build: _T, host: _T, target: _T): self.build = build self.host = host self.target = target - def __getitem__(self, machine: MachineChoice) -> typing.Optional[_T]: + def __getitem__(self, machine: MachineChoice) -> _T: return { MachineChoice.BUILD: self.build, MachineChoice.HOST: self.host, MachineChoice.TARGET: self.target }[machine] - def __setitem__(self, machine: MachineChoice, val: typing.Optional[_T]) -> None: + def __setitem__(self, machine: MachineChoice, val: _T) -> None: key = { MachineChoice.BUILD: 'build', MachineChoice.HOST: 'host', @@ -968,7 +966,7 @@ def Popen_safe(args, write=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, return p, o, e def Popen_safe_legacy(args, write=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs): - p = subprocess.Popen(args, universal_newlines=False, + p = subprocess.Popen(args, universal_newlines=False, close_fds=False, stdout=stdout, stderr=stderr, **kwargs) if write is not None: write = write.encode('utf-8') diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index 17af4df..8df8f48 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -873,7 +873,7 @@ Timeout: %4d return wrap def get_pretty_suite(self, test): - if len(self.suites) > 1: + if len(self.suites) > 1 and test.suite: rv = TestHarness.split_suite_string(test.suite[0])[0] s = "+".join(TestHarness.split_suite_string(s)[1] for s in test.suite) if len(s): diff --git a/run_project_tests.py b/run_project_tests.py index 4406e1e..324d824 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -505,6 +505,10 @@ def skippable(suite, test): if test.endswith('netcdf'): return True + # MSVC doesn't link with GFortran + if test.endswith('14 fortran links c'): + return True + # No frameworks test should be skipped on linux CI, as we expect all # prerequisites to be installed if mesonlib.is_linux(): diff --git a/run_unittests.py b/run_unittests.py index 5c9917a..2683e01 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -1813,48 +1813,48 @@ class AllPlatformTests(BasePlatformTests): self.init(testdir) self.build() - self.assertFailedTestCount(3, self.mtest_command) + self.assertFailedTestCount(4, self.mtest_command) self.assertFailedTestCount(0, self.mtest_command + ['--suite', ':success']) self.assertFailedTestCount(3, self.mtest_command + ['--suite', ':fail']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', ':success']) - self.assertFailedTestCount(0, self.mtest_command + ['--no-suite', ':fail']) + self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', ':success']) + self.assertFailedTestCount(1, self.mtest_command + ['--no-suite', ':fail']) self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj']) self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc']) self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail']) self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix']) - self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc']) - self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail']) - self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix']) + self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj']) + self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc']) + self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail']) + self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix']) self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj:fail']) self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'mainprj:success']) - self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'mainprj:fail']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj:success']) + self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj:fail']) + self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'mainprj:success']) self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail:fail']) self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjfail:success']) - self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail:fail']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail:success']) + self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail:fail']) + self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjfail:success']) self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:fail']) self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:success']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:fail']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjsucc:success']) + self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc:fail']) + self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc:success']) self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix:fail']) self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjmix:success']) - self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjmix:fail']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix:success']) + self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix:fail']) + self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjmix:success']) self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix:fail']) self.assertFailedTestCount(3, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj']) self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail']) self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail', 'mainprj-failing_test']) - self.assertFailedTestCount(1, self.mtest_command + ['--no-suite', 'subprjfail:fail', '--no-suite', 'subprjmix:fail']) + self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail:fail', '--no-suite', 'subprjmix:fail']) def test_build_by_default(self): testdir = os.path.join(self.common_test_dir, '134 build by default') diff --git a/test cases/common/216 link custom/meson.build b/test cases/common/216 link custom/meson.build index 5af27cd..c8d3a6d 100644 --- a/test cases/common/216 link custom/meson.build +++ b/test cases/common/216 link custom/meson.build @@ -16,6 +16,8 @@ clib = custom_target('linkcustom', '-o', '@OUTPUT@', '--private-dir', '@PRIVATE_DIR@'] + cc.cmd_array()) +# custom_target tests + exe = executable('prog', 'prog.c', link_with: clib) test('linkcustom', exe) @@ -33,3 +35,23 @@ d2 = declare_dependency(link_whole: clib) exe4 = executable('prog4', 'prog.c', dependencies: d2) test('linkwhole2', exe2) + +# custom_target[i] tests + +exe_i = executable('prog_i', 'prog.c', link_with: clib[0]) +test('linkcustom', exe_i) + +d_i = declare_dependency(link_with: clib[0]) + +exe2_i = executable('prog2_i', 'prog.c', dependencies: d_i) +test('linkcustom2_i', exe2_i) + +# Link whole tests + +exe3_i = executable('prog3_i', 'prog.c', link_whole: clib[0]) +test('linkwhole', exe) + +d2_i = declare_dependency(link_whole: clib[0]) + +exe4_i = executable('prog4_i', 'prog.c', dependencies: d2_i) +test('linkwhole2_i', exe2_i) diff --git a/test cases/common/217 link custom_i single from multiple/generate_conflicting_stlibs.py b/test cases/common/217 link custom_i single from multiple/generate_conflicting_stlibs.py new file mode 100644 index 0000000..42d6631 --- /dev/null +++ b/test cases/common/217 link custom_i single from multiple/generate_conflicting_stlibs.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +import shutil, sys, subprocess, argparse, pathlib + +parser = argparse.ArgumentParser() + +parser.add_argument('--private-dir', required=True) +parser.add_argument('-o', nargs='+', required=True) +parser.add_argument('cmparr', nargs='+') + +contents = [''' +int flob() { + return 0; +} +''', ''' +int flob() { + return 1; +} +'''] + +def generate_lib_gnulike(outfile, c_file, private_dir, compiler_array): + if shutil.which('ar'): + static_linker = 'ar' + elif shutil.which('llvm-ar'): + static_linker = 'llvm-ar' + elif shutil.which('gcc-ar'): + static_linker = 'gcc-ar' + else: + sys.exit('Could not detect a static linker.') + o_file = c_file.with_suffix('.o') + compile_cmd = compiler_array + ['-c', '-g', '-O2', '-o', str(o_file), str(c_file)] + subprocess.check_call(compile_cmd) + out_file = pathlib.Path(outfile) + if out_file.exists(): + out_file.unlink() + link_cmd = [static_linker, 'csr', outfile, str(o_file)] + subprocess.check_call(link_cmd) + return 0 + + +def generate_lib_msvc(outfile, c_file, private_dir, compiler_array): + static_linker = 'lib' + o_file = c_file.with_suffix('.obj') + compile_cmd = compiler_array + ['/MDd', + '/nologo', + '/ZI', + '/Ob0', + '/Od', + '/c', + '/Fo' + str(o_file), + str(c_file)] + subprocess.check_call(compile_cmd) + out_file = pathlib.Path(outfile) + if out_file.exists(): + out_file.unlink() + link_cmd = [static_linker, + '/nologo', + '/OUT:' + str(outfile), + str(o_file)] + subprocess.check_call(link_cmd) + return 0 + +def generate_lib(outfiles, private_dir, compiler_array): + private_dir = pathlib.Path(private_dir) + if not private_dir.exists(): + private_dir.mkdir() + + for i, content in enumerate(contents): + c_file = private_dir / ('flob_' + str(i + 1) + '.c') + c_file.write_text(content) + outfile = outfiles[i] + + cl_found = False + for cl_arg in compiler_array: + if (cl_arg.endswith('cl') or cl_arg.endswith('cl.exe')) and 'clang-cl' not in cl_arg: + ret = generate_lib_msvc(outfile, c_file, private_dir, compiler_array) + if ret > 0: + return ret + else: + cl_found = True + break + if not cl_found: + ret = generate_lib_gnulike(outfile, c_file, private_dir, compiler_array) + if ret > 0: + return ret + return 0 + +if __name__ == '__main__': + options = parser.parse_args() + sys.exit(generate_lib(options.o, options.private_dir, options.cmparr)) diff --git a/test cases/common/217 link custom_i single from multiple/meson.build b/test cases/common/217 link custom_i single from multiple/meson.build new file mode 100644 index 0000000..eee1fe1 --- /dev/null +++ b/test cases/common/217 link custom_i single from multiple/meson.build @@ -0,0 +1,37 @@ +project('linkcustom', 'c') + +# This would require passing the static linker to the build script or having +# it detect it by itself. I'm too lazy to implement it now and it is not +# really needed for testing that custom targets work. It is the responsibility +# of the custom target to produce things in the correct format. +assert(not meson.is_cross_build(), + 'MESON_SKIP_TEST cross checking not implemented.') + +cc = meson.get_compiler('c') +genprog = find_program('generate_conflicting_stlibs.py') + +clib = custom_target('linkcustom', + output: ['libflob_1.a', 'libflob_2.a'], + command: [genprog, + '-o', '@OUTPUT@', + '--private-dir', '@PRIVATE_DIR@'] + cc.cmd_array()) + +clib_2 = clib[1] + +exe = executable('prog', 'prog.c', link_with: clib_2) +test('linkcustom', exe) + +d = declare_dependency(link_with: clib_2) + +exe2 = executable('prog2', 'prog.c', dependencies: d) +test('linkcustom2', exe2) + +# Link whole tests + +exe3 = executable('prog3', 'prog.c', link_whole: clib_2) +test('linkwhole', exe) + +d2 = declare_dependency(link_whole: clib_2) + +exe4 = executable('prog4', 'prog.c', dependencies: d2) +test('linkwhole2', exe2) diff --git a/test cases/common/217 link custom_i single from multiple/prog.c b/test cases/common/217 link custom_i single from multiple/prog.c new file mode 100644 index 0000000..8013034 --- /dev/null +++ b/test cases/common/217 link custom_i single from multiple/prog.c @@ -0,0 +1,5 @@ +int flob(); + +int main(int argc, char **argv) { + return (flob() == 1 ? 0 : 1); +} diff --git a/test cases/common/218 link custom_i multiple from multiple/generate_stlibs.py b/test cases/common/218 link custom_i multiple from multiple/generate_stlibs.py new file mode 100644 index 0000000..5292006 --- /dev/null +++ b/test cases/common/218 link custom_i multiple from multiple/generate_stlibs.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 + +import shutil, sys, subprocess, argparse, pathlib + +parser = argparse.ArgumentParser() + +parser.add_argument('--private-dir', required=True) +parser.add_argument('-o', nargs='+', required=True) +parser.add_argument('cmparr', nargs='+') + +contents = ['''#include<stdio.h> + +void flob_1() { + printf("Now flobbing #1.\\n"); +} +''', '''#include<stdio.h> + +void flob_2() { + printf("Now flobbing #2.\\n"); +} +'''] + +def generate_lib_gnulike(outfile, c_file, private_dir, compiler_array): + if shutil.which('ar'): + static_linker = 'ar' + elif shutil.which('llvm-ar'): + static_linker = 'llvm-ar' + elif shutil.which('gcc-ar'): + static_linker = 'gcc-ar' + else: + sys.exit('Could not detect a static linker.') + o_file = c_file.with_suffix('.o') + compile_cmd = compiler_array + ['-c', '-g', '-O2', '-o', str(o_file), str(c_file)] + subprocess.check_call(compile_cmd) + out_file = pathlib.Path(outfile) + if out_file.exists(): + out_file.unlink() + link_cmd = [static_linker, 'csr', outfile, str(o_file)] + subprocess.check_call(link_cmd) + return 0 + + +def generate_lib_msvc(outfile, c_file, private_dir, compiler_array): + static_linker = 'lib' + o_file = c_file.with_suffix('.obj') + compile_cmd = compiler_array + ['/MDd', + '/nologo', + '/ZI', + '/Ob0', + '/Od', + '/c', + '/Fo' + str(o_file), + str(c_file)] + subprocess.check_call(compile_cmd) + out_file = pathlib.Path(outfile) + if out_file.exists(): + out_file.unlink() + link_cmd = [static_linker, + '/nologo', + '/OUT:' + str(outfile), + str(o_file)] + subprocess.check_call(link_cmd) + return 0 + +def generate_lib(outfiles, private_dir, compiler_array): + private_dir = pathlib.Path(private_dir) + if not private_dir.exists(): + private_dir.mkdir() + + for i, content in enumerate(contents): + c_file = private_dir / ('flob_' + str(i + 1) + '.c') + c_file.write_text(content) + outfile = outfiles[i] + + cl_found = False + for cl_arg in compiler_array: + if (cl_arg.endswith('cl') or cl_arg.endswith('cl.exe')) and 'clang-cl' not in cl_arg: + ret = generate_lib_msvc(outfile, c_file, private_dir, compiler_array) + if ret > 0: + return ret + else: + cl_found = True + break + if not cl_found: + ret = generate_lib_gnulike(outfile, c_file, private_dir, compiler_array) + if ret > 0: + return ret + return 0 + +if __name__ == '__main__': + options = parser.parse_args() + sys.exit(generate_lib(options.o, options.private_dir, options.cmparr)) diff --git a/test cases/common/218 link custom_i multiple from multiple/meson.build b/test cases/common/218 link custom_i multiple from multiple/meson.build new file mode 100644 index 0000000..e5236e5 --- /dev/null +++ b/test cases/common/218 link custom_i multiple from multiple/meson.build @@ -0,0 +1,37 @@ +project('linkcustom', 'c') + +# This would require passing the static linker to the build script or having +# it detect it by itself. I'm too lazy to implement it now and it is not +# really needed for testing that custom targets work. It is the responsibility +# of the custom target to produce things in the correct format. +assert(not meson.is_cross_build(), + 'MESON_SKIP_TEST cross checking not implemented.') + +cc = meson.get_compiler('c') +genprog = find_program('generate_stlibs.py') + +clib = custom_target('linkcustom', + output: ['libflob_1.a', 'libflob_2.a'], + command: [genprog, + '-o', '@OUTPUT@', + '--private-dir', '@PRIVATE_DIR@'] + cc.cmd_array()) + +clibs = [clib[0], clib[1]] + +exe = executable('prog', 'prog.c', link_with: clibs) +test('linkcustom', exe) + +d = declare_dependency(link_with: clibs) + +exe2 = executable('prog2', 'prog.c', dependencies: d) +test('linkcustom2', exe2) + +# Link whole tests + +exe3 = executable('prog3', 'prog.c', link_whole: clibs) +test('linkwhole', exe) + +d2 = declare_dependency(link_whole: clibs) + +exe4 = executable('prog4', 'prog.c', dependencies: d2) +test('linkwhole2', exe2) diff --git a/test cases/common/218 link custom_i multiple from multiple/prog.c b/test cases/common/218 link custom_i multiple from multiple/prog.c new file mode 100644 index 0000000..51effe6 --- /dev/null +++ b/test cases/common/218 link custom_i multiple from multiple/prog.c @@ -0,0 +1,8 @@ +void flob_1(); +void flob_2(); + +int main(int argc, char **argv) { + flob_1(); + flob_2(); + return 0; +} diff --git a/test cases/fortran/14 fortran links c/clib.c b/test cases/fortran/14 fortran links c/clib.c new file mode 100644 index 0000000..81b2e0c --- /dev/null +++ b/test cases/fortran/14 fortran links c/clib.c @@ -0,0 +1,7 @@ +#include <stdio.h> + +void hello(void){ + + printf("hello from C\n"); + +} diff --git a/test cases/fortran/14 fortran links c/f_call_c.f90 b/test cases/fortran/14 fortran links c/f_call_c.f90 new file mode 100644 index 0000000..af1e79c --- /dev/null +++ b/test cases/fortran/14 fortran links c/f_call_c.f90 @@ -0,0 +1,10 @@ +implicit none + +interface +subroutine hello() bind (c) +end subroutine hello +end interface + +call hello() + +end program diff --git a/test cases/fortran/14 fortran links c/meson.build b/test cases/fortran/14 fortran links c/meson.build new file mode 100644 index 0000000..163aec6 --- /dev/null +++ b/test cases/fortran/14 fortran links c/meson.build @@ -0,0 +1,13 @@ +project('Fortran calling C', 'fortran', 'c') + +ccid = meson.get_compiler('c').get_id() +if ccid == 'msvc' or ccid == 'clang-cl' + error('MESON_SKIP_TEST: MSVC and GCC do not interoperate like this.') +endif + +c_lib = library('clib', 'clib.c') + +f_call_c = executable('f_call_c', 'f_call_c.f90', + link_with: c_lib, + link_language: 'fortran') +test('Fortran calling C', f_call_c) diff --git a/test cases/objc/2 nsstring/meson.build b/test cases/objc/2 nsstring/meson.build index 7f2483f..94d2cf1 100644 --- a/test cases/objc/2 nsstring/meson.build +++ b/test cases/objc/2 nsstring/meson.build @@ -15,3 +15,6 @@ else endif exe = executable('stringprog', 'stringprog.m', dependencies : dep) test('stringtest', exe) + +# Ensure that a non-required dep that is not found does not cause an error +dependency('appleframeworks', modules: 'nonexisting', required: false) diff --git a/test cases/unit/4 suite selection/meson.build b/test cases/unit/4 suite selection/meson.build index d3d4e1a..ea6db92 100644 --- a/test cases/unit/4 suite selection/meson.build +++ b/test cases/unit/4 suite selection/meson.build @@ -11,3 +11,7 @@ test('mainprj-failing_test', test('mainprj-successful_test', executable('successful_test', 'successful_test.c'), suite : 'success') + +test('mainprj-successful_test_no_suite', + executable('no_suite_test', 'successful_test.c'), + suite : []) diff --git a/test cases/unit/4 suite selection/subprojects/subprjfail/meson.build b/test cases/unit/4 suite selection/subprojects/subprjfail/meson.build index d95f271..e6270a8 100644 --- a/test cases/unit/4 suite selection/subprojects/subprjfail/meson.build +++ b/test cases/unit/4 suite selection/subprojects/subprjfail/meson.build @@ -3,3 +3,7 @@ project('subprjfail', 'c') test('subprjfail-failing_test', executable('failing_test', 'failing_test.c'), suite : 'fail') + +test('subprjfail-failing_test_no_suite', + executable('failing_test_no_suite', 'failing_test.c'), + suite : []) diff --git a/test cases/unit/4 suite selection/subprojects/subprjsucc/meson.build b/test cases/unit/4 suite selection/subprojects/subprjsucc/meson.build index 8dafd65..b5ffaa4 100644 --- a/test cases/unit/4 suite selection/subprojects/subprjsucc/meson.build +++ b/test cases/unit/4 suite selection/subprojects/subprjsucc/meson.build @@ -3,3 +3,7 @@ project('subprjsucc', 'c') test('subprjsucc-successful_test', executable('successful_test', 'successful_test.c'), suite : 'success') + +test('subprjsucc-successful_test_no_suite', + executable('successful_test_no_suite', 'successful_test.c'), + suite : []) |