diff --git a/docs/markdown/snippets/add_native_both.md b/docs/markdown/snippets/add_native_both.md new file mode 100644 index 000000000000..b15d439219b1 --- /dev/null +++ b/docs/markdown/snippets/add_native_both.md @@ -0,0 +1,10 @@ +## Add a `native : 'both'`` value + +This has been added to the `add_language()` function. This allows explicitly +setting the default behavior of getting a language for both the host and build +machines. The default has been changed to `'both'`, and Meson will no longer +warn about getting no value for `native` in this function as a result. + +This as also been added to `add_global_args()`, `add_global_link_args()`, +`add_project_args()`, and `add_project_link_args()`. The default for these +remains the same, but `'both'` is now allowed diff --git a/docs/yaml/functions/add_global_arguments.yaml b/docs/yaml/functions/add_global_arguments.yaml index 3b26d10c8d07..3d739bab2483 100644 --- a/docs/yaml/functions/add_global_arguments.yaml +++ b/docs/yaml/functions/add_global_arguments.yaml @@ -26,13 +26,13 @@ kwargs: it in per-target flags. native: - type: bool + type: bool | str default: false since: 0.48.0 description: | - A boolean specifying whether the arguments should be - applied to the native or cross compilation. If `true` the arguments - will only be used for native compilations. If `false` the arguments - will only be used in cross compilations. If omitted, the flags are - added to native compilations if compiling natively and cross - compilations (only) when cross compiling. + A boolean or `'both'` specifying whether the arguments should be + applied to targets for the host machine, the build machine, or both. If + `true` the arguments will only be used for build targets. If `false` + the arguments will only be used for host targets. *(since 1.4.0)* If + `'both'` then arguments will be used for both host and build targets. + When host == build, all options are equivalent. diff --git a/docs/yaml/functions/add_languages.yaml b/docs/yaml/functions/add_languages.yaml index 3a77225aa7ce..63f93c99da90 100644 --- a/docs/yaml/functions/add_languages.yaml +++ b/docs/yaml/functions/add_languages.yaml @@ -43,9 +43,12 @@ kwargs: specified are not found. *(since 0.47.0)* The value of a [`feature`](Build-options.md#features) option can also be passed. native: - type: bool + type: bool | str since: 0.54.0 + default: 'both' description: | If set to `true`, the language will be used to compile for the build - machine, if `false`, for the host machine. + machine, if `false`, for the host machine. *(since 1.4.0)* can be set to + `'both'` (the only valid string value), which is the default. When + performing a host == build compilation all of these options are equivalent. diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index f2e84a822ee0..0851b30445f1 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2012-2021 The Meson development team +# Copyright © 2023 Intel Corporation from __future__ import annotations @@ -14,9 +15,11 @@ from .. import envconfig from ..wrap import wrap, WrapMode from .. import mesonlib -from ..mesonlib import (EnvironmentVariables, ExecutableSerialisation, MesonBugException, MesonException, HoldableObject, - FileMode, MachineChoice, OptionKey, listify, - extract_as_list, has_path_sep, path_is_in_root, PerMachine) +from ..mesonlib import (EnvironmentVariables, ExecutableSerialisation, + MesonBugException, MesonException, HoldableObject, + InterpreterMachineChoice, FileMode, MachineChoice, + OptionKey, listify, extract_as_list, has_path_sep, + path_is_in_root, PerMachine) from ..programs import ExternalProgram, NonExistingExternalProgram from ..dependencies import Dependency from ..depfile import DepFile @@ -76,6 +79,7 @@ INSTALL_TAG_KW, LANGUAGE_KW, NATIVE_KW, + NATIVE_BOTH_KW, PRESERVE_PATH_KW, REQUIRED_KW, SHARED_LIB_KWS, @@ -1303,7 +1307,7 @@ def func_project(self, node: mparser.FunctionNode, args: T.Tuple[str, T.List[str if not self.is_subproject(): self.check_stdlibs() - @typed_kwargs('add_languages', KwargInfo('native', (bool, NoneType), since='0.54.0'), REQUIRED_KW) + @typed_kwargs('add_languages', NATIVE_BOTH_KW.evolve(default='both', since='0.54.0'), REQUIRED_KW) @typed_pos_args('add_languages', varargs=str) def func_add_languages(self, node: mparser.FunctionNode, args: T.Tuple[T.List[str]], kwargs: 'kwtypes.FuncAddLanguages') -> bool: langs = args[0] @@ -1314,18 +1318,11 @@ def func_add_languages(self, node: mparser.FunctionNode, args: T.Tuple[T.List[st for lang in sorted(langs, key=compilers.sort_clink): mlog.log('Compiler for language', mlog.bold(lang), 'skipped: feature', mlog.bold(feature), 'disabled') return False - if native is not None: - return self.add_languages(langs, required, self.machine_from_native_kwarg(kwargs)) - else: - # absent 'native' means 'both' for backwards compatibility - 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=node) - - success = self.add_languages(langs, False, MachineChoice.BUILD) - success &= self.add_languages(langs, required, MachineChoice.HOST) - return success + if native is not InterpreterMachineChoice.BOTH: + return self.add_languages(langs, required, native.as_machinechoice()) + success = self.add_languages(langs, False, MachineChoice.BUILD) + success &= self.add_languages(langs, required, MachineChoice.HOST) + return success def _stringify_user_arguments(self, args: T.List[TYPE_var], func_name: str) -> T.List[str]: try: @@ -2862,29 +2859,49 @@ def func_add_test_setup(self, node: mparser.BaseNode, args: T.Tuple[str], kwargs kwargs['exclude_suites']) @typed_pos_args('add_global_arguments', varargs=str) - @typed_kwargs('add_global_arguments', NATIVE_KW, LANGUAGE_KW) + @typed_kwargs('add_global_arguments', NATIVE_BOTH_KW, LANGUAGE_KW) def func_add_global_arguments(self, node: mparser.FunctionNode, args: T.Tuple[T.List[str]], kwargs: 'kwtypes.FuncAddProjectArgs') -> None: - self._add_global_arguments(node, self.build.global_args[kwargs['native']], args[0], kwargs) + native = kwargs['native'] + if native is InterpreterMachineChoice.BOTH: + self._add_global_arguments(node, self.build.global_args[MachineChoice.HOST], args[0], kwargs) + self._add_global_arguments(node, self.build.global_args[MachineChoice.BUILD], args[0], kwargs) + else: + self._add_global_arguments(node, self.build.global_args[native.as_machinechoice()], args[0], kwargs) @typed_pos_args('add_global_link_arguments', varargs=str) - @typed_kwargs('add_global_arguments', NATIVE_KW, LANGUAGE_KW) + @typed_kwargs('add_global_arguments', NATIVE_BOTH_KW, LANGUAGE_KW) def func_add_global_link_arguments(self, node: mparser.FunctionNode, args: T.Tuple[T.List[str]], kwargs: 'kwtypes.FuncAddProjectArgs') -> None: - self._add_global_arguments(node, self.build.global_link_args[kwargs['native']], args[0], kwargs) + native = kwargs['native'] + if native is InterpreterMachineChoice.BOTH: + self._add_global_arguments(node, self.build.global_link_args[MachineChoice.HOST], args[0], kwargs) + self._add_global_arguments(node, self.build.global_link_args[MachineChoice.BUILD], args[0], kwargs) + else: + self._add_global_arguments(node, self.build.global_link_args[native.as_machinechoice()], args[0], kwargs) @typed_pos_args('add_project_arguments', varargs=str) - @typed_kwargs('add_project_arguments', NATIVE_KW, LANGUAGE_KW) + @typed_kwargs('add_project_arguments', NATIVE_BOTH_KW, LANGUAGE_KW) def func_add_project_arguments(self, node: mparser.FunctionNode, args: T.Tuple[T.List[str]], kwargs: 'kwtypes.FuncAddProjectArgs') -> None: - self._add_project_arguments(node, self.build.projects_args[kwargs['native']], args[0], kwargs) + native = kwargs['native'] + if native is InterpreterMachineChoice.BOTH: + self._add_project_arguments(node, self.build.projects_args[MachineChoice.BUILD], args[0], kwargs) + self._add_project_arguments(node, self.build.projects_args[MachineChoice.HOST], args[0], kwargs) + else: + self._add_project_arguments(node, self.build.projects_args[native.as_machinechoice()], args[0], kwargs) @typed_pos_args('add_project_link_arguments', varargs=str) - @typed_kwargs('add_global_arguments', NATIVE_KW, LANGUAGE_KW) + @typed_kwargs('add_global_arguments', NATIVE_BOTH_KW, LANGUAGE_KW) def func_add_project_link_arguments(self, node: mparser.FunctionNode, args: T.Tuple[T.List[str]], kwargs: 'kwtypes.FuncAddProjectArgs') -> None: - self._add_project_arguments(node, self.build.projects_link_args[kwargs['native']], args[0], kwargs) + native = kwargs['native'] + if native is InterpreterMachineChoice.BOTH: + self._add_project_arguments(node, self.build.projects_link_args[MachineChoice.BUILD], args[0], kwargs) + self._add_project_arguments(node, self.build.projects_link_args[MachineChoice.HOST], args[0], kwargs) + else: + self._add_project_arguments(node, self.build.projects_link_args[native.as_machinechoice()], args[0], kwargs) @FeatureNew('add_project_dependencies', '0.63.0') @typed_pos_args('add_project_dependencies', varargs=dependencies.Dependency) @typed_kwargs('add_project_dependencies', NATIVE_KW, LANGUAGE_KW) - def func_add_project_dependencies(self, node: mparser.FunctionNode, args: T.Tuple[T.List[dependencies.Dependency]], kwargs: 'kwtypes.FuncAddProjectArgs') -> None: + def func_add_project_dependencies(self, node: mparser.FunctionNode, args: T.Tuple[T.List[dependencies.Dependency]], kwargs: kwtypes.FuncAddProjectDeps) -> None: for_machine = kwargs['native'] for lang in kwargs['language']: if lang not in self.compilers[for_machine]: diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index 17f7876a04d0..dde27c6a9d14 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 -# Copyright © 2021 The Meson Developers -# Copyright © 2021 Intel Corporation +# Copyright 2021 The Meson Developers +# Copyright © 2021-2023 Intel Corporation from __future__ import annotations """Keyword Argument type annotations.""" @@ -9,11 +9,11 @@ from typing_extensions import TypedDict, Literal, Protocol, NotRequired -from .. import build +import build from .. import coredata from ..compilers import Compiler from ..dependencies.base import Dependency -from ..mesonlib import EnvironmentVariables, MachineChoice, File, FileMode, FileOrString, OptionKey +from ..mesonlib import EnvironmentVariables, MachineChoice, File, FileMode, FileOrString, OptionKey, InterpreterMachineChoice from ..modules.cmake import CMakeSubprojectOptions from ..programs import ExternalProgram from .type_checking import PkgConfigDefineType, SourcesVarargsType @@ -29,6 +29,12 @@ class FuncAddProjectArgs(TypedDict): a MachineChoice instance already. """ + native: InterpreterMachineChoice + language: T.List[str] + + +class FuncAddProjectDeps(TypedDict): + native: MachineChoice language: T.List[str] @@ -163,7 +169,7 @@ class FuncIncludeDirectories(TypedDict): class FuncAddLanguages(ExtractRequired): - native: T.Optional[bool] + native: InterpreterMachineChoice class RunTarget(TypedDict): diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index 616f4efbb58e..a867bf983df3 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -1,5 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 -# Copyright © 2021 Intel Corporation +# Copyright © 2021-2023 Intel Corporation """Helpers for strict type checking.""" @@ -14,7 +14,7 @@ from ..coredata import UserFeatureOption from ..dependencies import Dependency, InternalDependency from ..interpreterbase.decorators import KwargInfo, ContainerTypeInfo -from ..mesonlib import (File, FileMode, MachineChoice, listify, has_path_sep, +from ..mesonlib import (File, FileMode, MachineChoice, InterpreterMachineChoice, listify, has_path_sep, OptionKey, EnvironmentVariables) from ..programs import ExternalProgram @@ -168,6 +168,23 @@ def variables_convertor(contents: T.Union[str, T.List[str], T.Dict[str, str]]) - default=False, convertor=lambda n: MachineChoice.BUILD if n else MachineChoice.HOST) +_NATIVE_BOTH: T.Mapping[T.Union[bool, Literal['both']], InterpreterMachineChoice] = { + True: InterpreterMachineChoice.BUILD, + False: InterpreterMachineChoice.HOST, + 'both': InterpreterMachineChoice.BOTH, +} + +# We can't annotate the type below as Literal, but the validator will do that for us +# So, we need the type ignore, otherwise mypy complains about the `str` +NATIVE_BOTH_KW: KwargInfo[T.Union[bool, Literal['both']]] = KwargInfo( + 'native', + (bool, str), # type: ignore[arg-type] + default=False, + validator=lambda x: 'must be either a boolean or the literal "both"' if isinstance(x, str) and x != 'both' else None, + convertor=lambda x: _NATIVE_BOTH[x], + since_values={str: '1.4.0'}, +) + LANGUAGE_KW = KwargInfo( 'language', ContainerTypeInfo(list, str, allow_empty=False), listify=True, diff --git a/mesonbuild/utils/universal.py b/mesonbuild/utils/universal.py index 952d6f7b1588..e64988a376c5 100644 --- a/mesonbuild/utils/universal.py +++ b/mesonbuild/utils/universal.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2012-2020 The Meson development team - +# Copyright © 2023 Intel Corporation """A library of random helper functionality.""" @@ -24,7 +24,7 @@ import json from mesonbuild import mlog -from .core import MesonException, HoldableObject +from .core import MesonBugException, MesonException, HoldableObject if T.TYPE_CHECKING: from typing_extensions import Literal, Protocol @@ -61,6 +61,7 @@ class _VerPickleLoadable(Protocol): 'File', 'FileMode', 'GitException', + 'InterpreterMachineChoice', 'LibType', 'MachineChoice', 'EnvironmentException', @@ -486,6 +487,33 @@ def get_prefix(self) -> str: return PerMachine('build.', '')[self] +class InterpreterMachineChoice(enum.Enum): + + """Machine Choices for the interpreter. + + This includes the option of having a "both" value, which the interpreter + will turn into two calls, one for the build and one for the host machine (in + a cross build situation) + """ + + BUILD = enum.auto() + HOST = enum.auto() + BOTH = enum.auto() + + def as_machinechoice(self) -> MachineChoice: + """Convert to a normal MachineChoice + + a Both value cannot be converted, it is expected that the interpreter + will handle any logic duplication required to handle both cases. + + :raises MesonBugException: if a value of BOTH is converted + :return: A MachineChoiceValue + """ + if self.value == self.BOTH.value: + raise MesonBugException('Tried to get a MachineChoice for "both"') + return MachineChoice.HOST if self.value == self.HOST.value else MachineChoice.BUILD + + class PerMachine(T.Generic[_T]): def __init__(self, build: _T, host: _T) -> None: self.build = build diff --git a/test cases/common/115 subproject project arguments/meson.build b/test cases/common/115 subproject project arguments/meson.build index 90d4c05f33a4..ae62a4e6a550 100644 --- a/test cases/common/115 subproject project arguments/meson.build +++ b/test cases/common/115 subproject project arguments/meson.build @@ -2,16 +2,46 @@ project('project options tester', 'c', 'cpp', version : '2.3.4', license : 'mylicense') -add_global_arguments('-DGLOBAL_ARGUMENT', language: 'c') -add_project_arguments('-DPROJECT_OPTION', language: 'c') -add_project_arguments('-DPROJECT_OPTION_CPP', language: 'cpp') -add_project_arguments('-DPROJECT_OPTION_C_CPP', language: ['c', 'cpp']) +machine = get_option('machine') +assert(machine in ['host', 'build', 'both']) +if machine == 'host' + native = false +elif machine == 'build' + native = true +else + native = 'both' +endif -sub = subproject('subexe', version : '1.0.0') +if not add_languages('c', 'cpp', native : native, required : false) + error(f'MESON_SKIP_TEST this test requires compilers for @machine@ machine(s)') +endif -add_project_arguments('-DPROJECT_OPTION_1', language: 'c') +add_global_arguments('-DGLOBAL_ARGUMENT', language: 'c', native : native) +add_project_arguments('-DPROJECT_OPTION', language: 'c', native : native) +add_project_arguments('-DPROJECT_OPTION_CPP', language: 'cpp', native : native) +add_project_arguments('-DPROJECT_OPTION_C_CPP', language: ['c', 'cpp'], native : native) -e = executable('exe', 'exe.c') -e = executable('execpp', 'exe.cpp') +# XXX: when subprojects allow host/build +if machine != 'build' + sub = subproject('subexe', version : '1.0.0') +endif + +add_project_arguments('-DPROJECT_OPTION_1', language: 'c', native : native) + +if machine == 'both' + native = false +endif + +e = executable('exe', 'exe.c', native : native) +e = executable('execpp', 'exe.cpp', native : native) test('exetest', e) test('execpptest', e) + +if machine == 'both' + e = executable('exe_build', 'exe.c', native : true) + e = executable('execpp_build', 'exe.cpp', native : true) + if meson.can_run_host_binaries() + test('exetest_build', e) + test('execpptest_build', e) + endif +endif diff --git a/test cases/common/115 subproject project arguments/meson.options b/test cases/common/115 subproject project arguments/meson.options new file mode 100644 index 000000000000..399ee9e08e46 --- /dev/null +++ b/test cases/common/115 subproject project arguments/meson.options @@ -0,0 +1 @@ +option('machine', type : 'string', value : 'host') diff --git a/test cases/common/115 subproject project arguments/test.json b/test cases/common/115 subproject project arguments/test.json new file mode 100644 index 000000000000..ae17f6957500 --- /dev/null +++ b/test cases/common/115 subproject project arguments/test.json @@ -0,0 +1,11 @@ +{ + "matrix": { + "options": { + "machine": [ + { "val": "host" }, + { "val": "build" }, + { "val": "both" } + ] + } + } +} diff --git a/test cases/common/82 add language/meson.build b/test cases/common/82 add language/meson.build index e99f33a81548..621ec80a4f48 100644 --- a/test cases/common/82 add language/meson.build +++ b/test cases/common/82 add language/meson.build @@ -8,3 +8,11 @@ assert(not add_languages('klingon', required : false), 'Add_languages returned t test('C++', executable('cppprog', 'prog.cc')) add_languages('c', native: false) + +cpp = meson.get_compiler('cpp', native : false) + +# We have at least one CI setup that has only cross compilers, so both will fail +if add_languages('cpp', native : true, required : false) + assert(add_languages('cpp', native : 'both'), 'Add_languages returned false on success') + cpp_build = meson.get_compiler('cpp', native : true) +endif diff --git a/test cases/warning/2 languages missing native/meson.build b/test cases/warning/2 languages missing native/meson.build deleted file mode 100644 index e2047152b85e..000000000000 --- a/test cases/warning/2 languages missing native/meson.build +++ /dev/null @@ -1,3 +0,0 @@ -project('languages missing native', - meson_version : '>= 0.54') -add_languages('c') diff --git a/test cases/warning/2 languages missing native/test.json b/test cases/warning/2 languages missing native/test.json deleted file mode 100644 index f929654d82b6..000000000000 --- a/test cases/warning/2 languages missing native/test.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "stdout": [ - { - "line": "test cases/warning/2 languages missing native/meson.build:3: WARNING: add_languages is missing native:, assuming languages are wanted for both host and build." - } - ] -} diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index c15519874113..d95ca9a212d4 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2016-2021 The Meson development team +# Copyright © 2023 Intel Corporation import subprocess import re @@ -4920,3 +4921,11 @@ def test_c_cpp_stds(self): # The first supported std should be selected self.setconf('-Dcpp_std=c++11,gnu++11,vc++11') self.assertEqual(self.getconf('cpp_std'), 'c++11') + + def test_add_arguments_both_no_dups(self) -> None: + testdir = os.path.join(self.common_test_dir, '115 subproject project arguments') + self.init(testdir, extra_args=['-Dmachine=both']) + compdb = self.get_compdb() + # It really doesn't matter what target we look at + self.assertEqual(compdb[0]['command'].count('-DGLOBAL_ARGUMENT'), 1, + msg="`native : both` inserted arguments twice in the same target") diff --git a/unittests/baseplatformtests.py b/unittests/baseplatformtests.py index ec3f18908466..5d090e498710 100644 --- a/unittests/baseplatformtests.py +++ b/unittests/baseplatformtests.py @@ -1,5 +1,8 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2016-2021 The Meson development team +# Copyright © 2023 Intel Corporation + +from __future__ import annotations from pathlib import PurePath from unittest import mock, TestCase, SkipTest @@ -33,6 +36,17 @@ run_mtest_inprocess, handle_meson_skip_test, ) +if T.TYPE_CHECKING: + from typing_extensions import TypedDict + + class CompDBEntry(TypedDict): + directory: str + command: str + file: str + output: str + + CompDBType = T.List[CompDBEntry] + # magic attribute used by unittest.result.TestResult._is_relevant_tb_level # This causes tracebacks to hide these internal implementation details, @@ -306,12 +320,12 @@ def utime(self, f): ensure_backend_detects_changes(self.backend) os.utime(f) - def get_compdb(self): + def get_compdb(self) -> CompDBType: if self.backend is not Backend.ninja: raise SkipTest(f'Compiler db not available with {self.backend.name} backend') try: with open(os.path.join(self.builddir, 'compile_commands.json'), encoding='utf-8') as ifile: - contents = json.load(ifile) + contents: CompDBType = json.load(ifile) except FileNotFoundError: raise SkipTest('Compiler db not found') # If Ninja is using .rsp files, generate them, read their contents, and