From 0810faaba3653ccba02e469e85351338c6089231 Mon Sep 17 00:00:00 2001 From: DongHun Kwak Date: Tue, 4 Jan 2022 15:38:48 +0900 Subject: [PATCH] Imported Upstream version 0.56.1 --- cross/linux-mingw-w64-32bit.txt | 2 + cross/linux-mingw-w64-64bit.txt | 2 + docs/markdown/Pkgconfig-module.md | 3 +- docs/markdown/Qt5-module.md | 3 +- man/meson.1 | 2 +- mesonbuild/backend/ninjabackend.py | 5 +- mesonbuild/build.py | 9 +- mesonbuild/cmake/interpreter.py | 52 ++++++++-- mesonbuild/compilers/c.py | 29 +++++- mesonbuild/compilers/compilers.py | 2 +- mesonbuild/compilers/mixins/emscripten.py | 4 +- mesonbuild/compilers/rust.py | 4 + mesonbuild/coredata.py | 2 +- mesonbuild/dependencies/base.py | 5 +- mesonbuild/environment.py | 67 ++++++++---- mesonbuild/interpreter.py | 26 +++-- mesonbuild/linkers.py | 7 +- mesonbuild/modules/pkgconfig.py | 9 +- mesonbuild/modules/qt.py | 2 +- mesonbuild/msubprojects.py | 2 +- mesonbuild/scripts/depfixer.py | 2 +- mesonbuild/wrap/wrap.py | 12 ++- run_unittests.py | 114 ++++++++------------- .../2 advanced/subprojects/cmMod/CMakeLists.txt | 10 ++ .../2 advanced/subprojects/cmMod/lib/cmMod.cpp | 9 ++ test cases/cmake/24 mixing languages/main.c | 5 + test cases/cmake/24 mixing languages/meson.build | 13 +++ .../subprojects/cmTest/CMakeLists.txt | 8 ++ .../subprojects/cmTest/cmTest.c | 13 +++ .../subprojects/cmTest/cmTest.h | 3 + .../subprojects/cmTest/cmTest.m | 7 ++ .../122 llvm ir and assembly/square-aarch64.S | 11 +- .../76 link with shared module on osx/test.json | 2 +- .../85 nested subproject regenerate depends/main.c | 3 + .../meson.build | 6 ++ .../subprojects/sub1/meson.build | 4 + .../subprojects/sub2/CMakeLists.txt | 1 + 37 files changed, 326 insertions(+), 134 deletions(-) create mode 100644 test cases/cmake/24 mixing languages/main.c create mode 100644 test cases/cmake/24 mixing languages/meson.build create mode 100644 test cases/cmake/24 mixing languages/subprojects/cmTest/CMakeLists.txt create mode 100644 test cases/cmake/24 mixing languages/subprojects/cmTest/cmTest.c create mode 100644 test cases/cmake/24 mixing languages/subprojects/cmTest/cmTest.h create mode 100644 test cases/cmake/24 mixing languages/subprojects/cmTest/cmTest.m create mode 100644 test cases/unit/85 nested subproject regenerate depends/main.c create mode 100644 test cases/unit/85 nested subproject regenerate depends/meson.build create mode 100644 test cases/unit/85 nested subproject regenerate depends/subprojects/sub1/meson.build create mode 100644 test cases/unit/85 nested subproject regenerate depends/subprojects/sub2/CMakeLists.txt diff --git a/cross/linux-mingw-w64-32bit.txt b/cross/linux-mingw-w64-32bit.txt index a62f57f..caf1da1 100644 --- a/cross/linux-mingw-w64-32bit.txt +++ b/cross/linux-mingw-w64-32bit.txt @@ -1,12 +1,14 @@ [binaries] c = '/usr/bin/i686-w64-mingw32-gcc' cpp = '/usr/bin/i686-w64-mingw32-g++' +objc = '/usr/bin/i686-w64-mingw32-gcc' ar = '/usr/bin/i686-w64-mingw32-ar' strip = '/usr/bin/i686-w64-mingw32-strip' pkgconfig = '/usr/bin/i686-w64-mingw32-pkg-config' windres = '/usr/bin/i686-w64-mingw32-windres' exe_wrapper = 'wine' ld = '/usr/bin/i686-w64-mingw32-ld' +cmake = '/usr/bin/cmake' [properties] # Directory that contains 'bin', 'lib', etc diff --git a/cross/linux-mingw-w64-64bit.txt b/cross/linux-mingw-w64-64bit.txt index 36d7350..f49fb35 100644 --- a/cross/linux-mingw-w64-64bit.txt +++ b/cross/linux-mingw-w64-64bit.txt @@ -1,11 +1,13 @@ [binaries] c = '/usr/bin/x86_64-w64-mingw32-gcc' cpp = '/usr/bin/x86_64-w64-mingw32-g++' +objc = '/usr/bin/x86_64-w64-mingw32-gcc' ar = '/usr/bin/x86_64-w64-mingw32-ar' strip = '/usr/bin/x86_64-w64-mingw32-strip' pkgconfig = '/usr/bin/x86_64-w64-mingw32-pkg-config' windres = '/usr/bin/x86_64-w64-mingw32-windres' exe_wrapper = 'wine64' +cmake = '/usr/bin/cmake' [properties] # Directory that contains 'bin', 'lib', etc diff --git a/docs/markdown/Pkgconfig-module.md b/docs/markdown/Pkgconfig-module.md index e9aa4a2..53c23fa 100644 --- a/docs/markdown/Pkgconfig-module.md +++ b/docs/markdown/Pkgconfig-module.md @@ -50,7 +50,8 @@ keyword arguments. reference other pkgconfig variables, e.g. `datadir=${prefix}/share`. The names `prefix`, `libdir` and `includedir` are reserved and may not be used. *Since 0.56.0* it can also be a - dictionary. + dictionary but ordering of Meson dictionaries are not guaranteed, which could + cause issues when some variables reference other variables. - `version` a string describing the version of this library, used to set the `Version:` field. (*since 0.46.0*) Defaults to the project version if unspecified. - `d_module_versions` a list of module version flags used when compiling diff --git a/docs/markdown/Qt5-module.md b/docs/markdown/Qt5-module.md index 9665267..6826460 100644 --- a/docs/markdown/Qt5-module.md +++ b/docs/markdown/Qt5-module.md @@ -79,7 +79,8 @@ executable('myprog', 'main.cpp', 'myclass.cpp', moc_files, ``` Sometimes, translations are embedded inside the binary using qresource files. -In this case the ts files do not need to be explicitly listed. For example: +In this case the ts files do not need to be explicitly listed, but will be +inferred from the built qm files listed in the qresource file. For example: ```meson qt5 = import('qt5') diff --git a/man/meson.1 b/man/meson.1 index d2c6453..389ebdb 100644 --- a/man/meson.1 +++ b/man/meson.1 @@ -1,4 +1,4 @@ -.TH MESON "1" "October 2020" "meson 0.56.0" "User Commands" +.TH MESON "1" "January 2021" "meson 0.56.1" "User Commands" .SH NAME meson - a high productivity build system .SH DESCRIPTION diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 2ffbb85..2370b55 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1489,7 +1489,7 @@ int dummy; self.create_target_source_introspection(target, valac, args, all_files, []) return other_src[0], other_src[1], vala_c_src - def generate_rust_target(self, target): + def generate_rust_target(self, target: build.BuildTarget) -> None: rustc = target.compilers['rust'] # Rust compiler takes only the main file as input and # figures out what other files are needed via import @@ -1533,7 +1533,8 @@ int dummy; depfile = os.path.join(target.subdir, target.name + '.d') args += ['--emit', 'dep-info={}'.format(depfile), '--emit', 'link'] args += target.get_extra_args('rust') - args += ['-o', os.path.join(target.subdir, target.get_filename())] + args += rustc.get_output_args(os.path.join(target.subdir, target.get_filename())) + args += self.environment.coredata.get_external_args(target.for_machine, rustc.language) orderdeps = [os.path.join(t.subdir, t.get_filename()) for t in target.link_targets] linkdirs = OrderedDict() for d in target.link_targets: diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 78292f2..36d4e19 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -1359,11 +1359,12 @@ You probably should put it in link_with instead.''') for link_target in self.link_targets: if isinstance(link_target, SharedModule): if self.environment.machines[self.for_machine].is_darwin(): - raise MesonException('''target links against shared modules. -This is not permitted on OSX''') + raise MesonException( + 'target links against shared modules. 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''') + mlog.warning('target links against shared modules. This ' + 'is not recommended as it is not supported on some ' + 'platforms') return class Generator: diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index e2f11a0..24395d3 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -231,7 +231,7 @@ class ConverterTarget: if target.install_paths: self.install_dir = target.install_paths[0] - self.languages = [] # type: T.List[str] + self.languages = set() # type: T.Set[str] self.sources = [] # type: T.List[Path] self.generated = [] # type: T.List[Path] self.generated_ctgt = [] # type: T.List[CustomTargetReference] @@ -250,19 +250,40 @@ class ConverterTarget: self.name = _sanitize_cmake_name(self.name) self.generated_raw = [] # type: T.List[Path] + for i in target.files: - # Determine the meson language + languages = set() # type: T.Set[str] + src_suffixes = set() # type: T.Set[str] + + # Insert suffixes + for j in i.sources: + if not j.suffix: + continue + src_suffixes.add(j.suffix[1:]) + + # Determine the meson language(s) + # Extract the default language from the explicit CMake field lang_cmake_to_meson = {val.lower(): key for key, val in language_map.items()} - lang = lang_cmake_to_meson.get(i.language.lower(), 'c') - if lang not in self.languages: - self.languages += [lang] - if lang not in self.compile_opts: - self.compile_opts[lang] = [] + languages.add(lang_cmake_to_meson.get(i.language.lower(), 'c')) + + # Determine missing languages from the source suffixes + for sfx in src_suffixes: + for key, val in lang_suffixes.items(): + if sfx in val: + languages.add(key) + break + + # Register the new languages and initialize the compile opts array + for lang in languages: + self.languages.add(lang) + if lang not in self.compile_opts: + self.compile_opts[lang] = [] # Add arguments, but avoid duplicates args = i.flags args += ['-D{}'.format(x) for x in i.defines] - self.compile_opts[lang] += [x for x in args if x not in self.compile_opts[lang]] + for lang in languages: + self.compile_opts[lang] += [x for x in args if x not in self.compile_opts[lang]] # Handle include directories self.includes += [x.path for x in i.includes if x.path not in self.includes and not x.isSystem] @@ -502,6 +523,20 @@ class ConverterTarget: self.link_libraries = [x for x in self.link_libraries if x.lower() not in blacklist_link_libs] self.link_flags = [x for x in self.link_flags if check_flag(x)] + # Handle OSX frameworks + def handle_frameworks(flags: T.List[str]) -> T.List[str]: + res: T.List[str] = [] + for i in flags: + p = Path(i) + if not p.exists() or not p.name.endswith('.framework'): + res += [i] + continue + res += ['-framework', p.stem] + return res + + self.link_libraries = handle_frameworks(self.link_libraries) + self.link_flags = handle_frameworks(self.link_flags) + # Handle explicit CMake add_dependency() calls for i in self.depends_raw: dep_tgt = output_target_map.target(i) @@ -850,6 +885,7 @@ class CMakeInterpreter: def configure(self, extra_cmake_options: T.List[str]) -> CMakeExecutor: # Find CMake + # TODO: Using MachineChoice.BUILD should always be correct here, but also evaluate the use of self.for_machine cmake_exe = CMakeExecutor(self.env, '>=3.7', MachineChoice.BUILD) if not cmake_exe.found(): raise CMakeException('Unable to find CMake') diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 95c4698..fd3ebc0 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -428,7 +428,11 @@ class VisualStudioCCompiler(MSVCCompiler, VisualStudioLikeCCompilerMixin, CCompi def get_options(self) -> 'OptionDictType': opts = super().get_options() - c_stds = ['none', 'c89', 'c99', 'c11'] + c_stds = ['none', 'c89', 'c99', 'c11', + # Need to have these to be compatible with projects + # that set c_std to e.g. gnu99. + # https://github.com/mesonbuild/meson/issues/7611 + 'gnu89', 'gnu90', 'gnu9x', 'gnu99', 'gnu1x', 'gnu11'] opts.update({ 'std': coredata.UserComboOption( 'C language standard to use', @@ -442,8 +446,8 @@ class VisualStudioCCompiler(MSVCCompiler, VisualStudioLikeCCompilerMixin, CCompi args = [] std = options['std'] # As of MVSC 16.7, /std:c11 is the only valid C standard option. - if std.value in {'c11'}: - args.append('/std:' + std.value) + if std.value in {'c11', 'gnu11'}: + args.append('/std:c11') return args @@ -458,6 +462,25 @@ class ClangClCCompiler(ClangClCompiler, VisualStudioLikeCCompilerMixin, CCompile full_version=full_version) ClangClCompiler.__init__(self, target) + def get_options(self) -> 'OptionDictType': + # Clang-cl can compile up to c99, but doesn't have a std-swtich for + # them. Unlike recent versions of MSVC it doesn't (as of 10.0.1) + # support c11 + opts = super().get_options() + c_stds = ['none', 'c89', 'c99', + # Need to have these to be compatible with projects + # that set c_std to e.g. gnu99. + # https://github.com/mesonbuild/meson/issues/7611 + 'gnu89', 'gnu90', 'gnu9x', 'gnu99'] + opts.update({ + 'std': coredata.UserComboOption( + 'C language standard to use', + c_stds, + 'none', + ), + }) + return opts + class IntelClCCompiler(IntelVisualStudioLikeCompiler, VisualStudioLikeCCompilerMixin, CCompiler): diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 0f074bb..4e9b86b 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -785,7 +785,7 @@ class Compiler(metaclass=abc.ABCMeta): # On Windows antivirus programs and the like hold on to files so # they can't be deleted. There's not much to do in this case. Also, # catch OSError because the directory is then no longer empty. - yield None + return @contextlib.contextmanager def cached_compile(self, code: str, cdata: coredata.CoreData, *, diff --git a/mesonbuild/compilers/mixins/emscripten.py b/mesonbuild/compilers/mixins/emscripten.py index 87bc40c..cd18b35 100644 --- a/mesonbuild/compilers/mixins/emscripten.py +++ b/mesonbuild/compilers/mixins/emscripten.py @@ -50,7 +50,7 @@ class EmscriptenMixin(Compiler): def thread_link_flags(self, env: 'Environment') -> T.List[str]: args = ['-s', 'USE_PTHREADS=1'] - count = env.coredata.compiler_options[self.for_machine]['{}_thread_count'.format(self.language)].value # type: int + count = env.coredata.compiler_options[self.for_machine][self.language]['thread_count'].value # type: int if count: args.extend(['-s', 'PTHREAD_POOL_SIZE={}'.format(count)]) return args @@ -58,7 +58,7 @@ class EmscriptenMixin(Compiler): def get_options(self) -> 'coredata.OptionDictType': opts = super().get_options() opts.update({ - '{}_thread_count'.format(self.language): coredata.UserIntegerOption( + 'thread_count': coredata.UserIntegerOption( 'Number of threads to use in web assembly, set to 0 to disable', (0, None, 4), # Default was picked at random ), diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index 02bc791..33ffa77 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -119,6 +119,10 @@ class RustCompiler(Compiler): def get_output_args(self, outputname: str) -> T.List[str]: return ['-o', outputname] + @classmethod + def use_linker_args(cls, linker: str) -> T.List[str]: + return ['-C', 'linker={}'.format(linker)] + # Rust does not have a use_linker_args because it dispatches to a gcc-like # C compiler for dynamic linking, as such we invoke the C compiler's # use_linker_args method instead. diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py index 252b1a7..7befa9a 100644 --- a/mesonbuild/coredata.py +++ b/mesonbuild/coredata.py @@ -39,7 +39,7 @@ if T.TYPE_CHECKING: OptionDictType = T.Union[T.Dict[str, 'UserOption[T.Any]'], OptionOverrideProxy] CompilerCheckCacheKey = T.Tuple[T.Tuple[str, ...], str, str, T.Tuple[str, ...], str] -version = '0.56.0' +version = '0.56.1' backendlist = ['ninja', 'vs', 'vs2010', 'vs2015', 'vs2017', 'vs2019', 'xcode'] default_yielding = False diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 3a5f5f8..1abd288 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -1087,10 +1087,11 @@ class CMakeDependency(ExternalDependency): # AttributeError exceptions in derived classes self.traceparser = None # type: CMakeTraceParser - self.cmakebin = CMakeExecutor(environment, CMakeDependency.class_cmake_version, MachineChoice.BUILD, silent=self.silent) + # TODO further evaluate always using MachineChoice.BUILD + self.cmakebin = CMakeExecutor(environment, CMakeDependency.class_cmake_version, self.for_machine, silent=self.silent) if not self.cmakebin.found(): self.cmakebin = None - msg = 'No CMake binary for machine {} not found. Giving up.'.format(MachineChoice.BUILD) + msg = 'No CMake binary for machine {} not found. Giving up.'.format(self.for_machine) if self.required: raise DependencyException(msg) mlog.debug(msg) diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 588005b..7194d03 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -129,6 +129,9 @@ from .compilers import ( VisualStudioCPPCompiler, ) +if T.TYPE_CHECKING: + from .dependencies import ExternalProgram + build_filename = 'meson.build' CompilersDict = T.Dict[str, Compiler] @@ -869,7 +872,7 @@ class Environment: defines[rest[0]] = rest[1] return defines - def _get_compilers(self, lang, for_machine): + def _get_compilers(self, lang: str, for_machine: MachineChoice) -> T.Tuple[T.List[T.List[str]], T.List[str], T.Optional['ExternalProgram']]: ''' The list of compilers is detected in the exact same way for C, C++, ObjC, ObjC++, Fortran, CS so consolidate it here. @@ -1062,9 +1065,17 @@ class Environment: self.__failed_to_detect_linker(compiler, check_args, o, e) return linker - def _detect_c_or_cpp_compiler(self, lang: str, for_machine: MachineChoice) -> Compiler: + def _detect_c_or_cpp_compiler(self, lang: str, for_machine: MachineChoice, *, override_compiler: T.Optional[T.List[str]] = None) -> Compiler: + """Shared implementation for finding the C or C++ compiler to use. + + the override_compiler option is provided to allow compilers which use + the compiler (GCC or Clang usually) as their shared linker, to find + the linker they need. + """ popen_exceptions = {} compilers, ccache, exe_wrap = self._get_compilers(lang, for_machine) + if override_compiler is not None: + compilers = [override_compiler] is_cross = self.is_cross_build(for_machine) info = self.machines[for_machine] @@ -1619,9 +1630,9 @@ class Environment: return comp_class(exelist, version, for_machine, info, is_cross) raise EnvironmentException('Unknown compiler "' + ' '.join(exelist) + '"') - def detect_rust_compiler(self, for_machine): - popen_exceptions = {} - compilers, ccache, exe_wrap = self._get_compilers('rust', for_machine) + def detect_rust_compiler(self, for_machine: MachineChoice) -> RustCompiler: + popen_exceptions = {} # type: T.Dict[str, Exception] + compilers, _, exe_wrap = self._get_compilers('rust', for_machine) is_cross = self.is_cross_build(for_machine) info = self.machines[for_machine] @@ -1634,7 +1645,7 @@ class Environment: compiler = [compiler] arg = ['--version'] try: - p, out = Popen_safe(compiler + arg)[0:2] + out = Popen_safe(compiler + arg)[1] except OSError as e: popen_exceptions[' '.join(compiler + arg)] = e continue @@ -1651,17 +1662,30 @@ class Environment: # the default use that, and second add the necessary arguments # to rust to use -fuse-ld + if any(a.startswith('linker=') for a in compiler): + mlog.warning( + 'Please do not put -C linker= in your compiler ' + 'command, set rust_ld=command in your cross file ' + 'or use the RUST_LD environment variable. meson ' + 'will override your seletion otherwise.') + if override is None: extra_args = {} always_args = [] if is_link_exe: - compiler.extend(['-C', 'linker={}'.format(cc.linker.exelist[0])]) + compiler.extend(RustCompiler.use_linker_args(cc.linker.exelist[0])) extra_args['direct'] = True extra_args['machine'] = cc.linker.machine - elif not ((info.is_darwin() and isinstance(cc, AppleClangCCompiler)) or - isinstance(cc, GnuCCompiler)): - c = cc.exelist[1] if cc.exelist[0].endswith('ccache') else cc.exelist[0] - compiler.extend(['-C', 'linker={}'.format(c)]) + else: + exelist = cc.linker.exelist.copy() + if 'ccache' in exelist[0]: + del exelist[0] + c = exelist.pop(0) + compiler.extend(RustCompiler.use_linker_args(c)) + + # Also ensure that we pass any extra arguments to the linker + for l in exelist: + compiler.extend(['-C', 'link-arg={}'.format(l)]) # This trickery with type() gets us the class of the linker # so we can initialize a new copy for the Rust Compiler @@ -1675,21 +1699,22 @@ class Environment: elif 'link' in override[0]: linker = self._guess_win_linker( override, RustCompiler, for_machine, use_linker_prefix=False) + # rustc takes linker arguments without a prefix, and + # inserts the correct prefix itself. linker.direct = True + compiler.extend(RustCompiler.use_linker_args(linker.exelist[0])) else: - # We're creating a new type of "C" compiler, that has rust - # as it's language. This is gross, but I can't figure out - # another way to handle this, because rustc is actually - # invoking the c compiler as it's linker. - b = type('b', (type(cc), ), {}) - b.language = RustCompiler.language - linker = self._guess_nix_linker(cc.exelist, b, for_machine) + # On linux and macos rust will invoke the c compiler for + # linking, on windows it will use lld-link or link.exe. + # we will simply ask for the C compiler that coresponds to + # it, and use that. + cc = self._detect_c_or_cpp_compiler('c', for_machine, override_compiler=override) + linker = cc.linker # Of course, we're not going to use any of that, we just # need it to get the proper arguments to pass to rustc - c = cc.exelist[1] if cc.exelist[0].endswith('ccache') else cc.exelist[0] - compiler.extend(['-C', 'linker={}'.format(c)]) - compiler.extend(['-C', 'link-args={}'.format(' '.join(cc.use_linker_args(override[0])))]) + c = linker.exelist[1] if linker.exelist[0].endswith('ccache') else linker.exelist[0] + compiler.extend(RustCompiler.use_linker_args(c)) self.coredata.add_lang_args(RustCompiler.language, RustCompiler, for_machine, self) return RustCompiler( diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 6222f97..03b39e7 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -2383,6 +2383,7 @@ class Interpreter(InterpreterBase): default_project_options: T.Optional[T.Dict[str, str]] = None, mock: bool = False, ast: T.Optional[mparser.CodeBlockNode] = None, + is_translated: bool = False, ) -> None: super().__init__(build.environment.get_source_dir(), subdir, subproject) self.an_unpicklable_object = mesonlib.an_unpicklable_object @@ -2421,8 +2422,16 @@ class Interpreter(InterpreterBase): self.default_project_options = {} self.project_default_options = {} self.build_func_dict() + # build_def_files needs to be defined before parse_project is called - self.build_def_files = [os.path.join(self.subdir, environment.build_filename)] + # + # For non-meson subprojects, we'll be using the ast. Even if it does + # exist we don't want to add a dependency on it, it's autogenerated + # from the actual build files, and is just for reference. + self.build_def_files = [] + build_filename = os.path.join(self.subdir, environment.build_filename) + if not is_translated: + self.build_def_files.append(build_filename) if not mock: self.parse_project() self._redetect_machines() @@ -2690,7 +2699,7 @@ class Interpreter(InterpreterBase): varlist = mesonlib.stringlistify(variables) if list_new: FeatureNew.single_use('variables as list of strings', '0.56.0', self.subproject) - variables = {} + variables = collections.OrderedDict() for v in varlist: try: (key, value) = v.split('=', 1) @@ -2941,11 +2950,14 @@ external dependencies (including libraries) must go to "dependencies".''') return self.disabled_subproject(subp_name, exception=e) raise e - def _do_subproject_meson(self, subp_name, subdir, default_options, kwargs, ast=None, build_def_files=None): + def _do_subproject_meson(self, subp_name: str, subdir: str, default_options, kwargs, + ast: T.Optional[mparser.CodeBlockNode] = None, + build_def_files: T.Optional[T.List[str]] = None, + is_translated: bool = False) -> SubprojectHolder: with mlog.nested(): new_build = self.build.copy() subi = Interpreter(new_build, self.backend, subp_name, subdir, self.subproject_dir, - self.modules, default_options, ast=ast) + self.modules, default_options, ast=ast, is_translated=is_translated) subi.subprojects = self.subprojects subi.subproject_stack = self.subproject_stack + [subp_name] @@ -2971,8 +2983,8 @@ external dependencies (including libraries) must go to "dependencies".''') # Duplicates are possible when subproject uses files from project root if build_def_files: self.build_def_files = list(set(self.build_def_files + build_def_files)) - else: - self.build_def_files = list(set(self.build_def_files + subi.build_def_files)) + # We always need the subi.build_def_files, to propgate sub-sub-projects + self.build_def_files = list(set(self.build_def_files + subi.build_def_files)) self.build.merge(subi.build) self.build.subprojects[subp_name] = subi.project_version self.summary.update(subi.summary) @@ -3016,7 +3028,7 @@ external dependencies (including libraries) must go to "dependencies".''') mlog.cmd_ci_include(meson_filename) mlog.log() - result = self._do_subproject_meson(subp_name, subdir, default_options, kwargs, ast, cm_int.bs_files) + result = self._do_subproject_meson(subp_name, subdir, default_options, kwargs, ast, cm_int.bs_files, is_translated=True) result.cm_interpreter = cm_int mlog.log() diff --git a/mesonbuild/linkers.py b/mesonbuild/linkers.py index 589945c..67cfb6b 100644 --- a/mesonbuild/linkers.py +++ b/mesonbuild/linkers.py @@ -1219,8 +1219,13 @@ class SolarisDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): return ([], set()) processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir) all_paths = mesonlib.OrderedSet([os.path.join('$ORIGIN', p) for p in processed_rpaths]) + rpath_dirs_to_remove = set() + for p in all_paths: + rpath_dirs_to_remove.add(p.encode('utf8')) if build_rpath != '': all_paths.add(build_rpath) + for p in build_rpath.split(':'): + rpath_dirs_to_remove.add(p.encode('utf8')) # In order to avoid relinking for RPATH removal, the binary needs to contain just # enough space in the ELF header to hold the final installation RPATH. @@ -1231,7 +1236,7 @@ class SolarisDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): paths = padding else: paths = paths + ':' + padding - return (self._apply_prefix('-rpath,{}'.format(paths)), set()) + return (self._apply_prefix('-rpath,{}'.format(paths)), rpath_dirs_to_remove) def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str], diff --git a/mesonbuild/modules/pkgconfig.py b/mesonbuild/modules/pkgconfig.py index a863b33..62e1c15 100644 --- a/mesonbuild/modules/pkgconfig.py +++ b/mesonbuild/modules/pkgconfig.py @@ -131,7 +131,7 @@ class DependenciesHelper: if obj.found(): processed_libs += obj.get_link_args() processed_cflags += obj.get_compile_args() - self._add_lib_dependencies(obj.libraries, obj.whole_libraries, obj.ext_deps, public) + self._add_lib_dependencies(obj.libraries, obj.whole_libraries, obj.ext_deps, public, private_external_deps=True) elif isinstance(obj, dependencies.Dependency): if obj.found(): processed_libs += obj.get_link_args() @@ -160,7 +160,7 @@ class DependenciesHelper: return processed_libs, processed_reqs, processed_cflags - def _add_lib_dependencies(self, link_targets, link_whole_targets, external_deps, public): + def _add_lib_dependencies(self, link_targets, link_whole_targets, external_deps, public, private_external_deps=False): add_libs = self.add_pub_libs if public else self.add_priv_libs # Recursively add all linked libraries for t in link_targets: @@ -173,7 +173,10 @@ class DependenciesHelper: for t in link_whole_targets: self._add_link_whole(t, public) # And finally its external dependencies - self.add_priv_libs(external_deps) + if private_external_deps: + self.add_priv_libs(external_deps) + else: + add_libs(external_deps) def _add_link_whole(self, t, public): # Don't include static libraries that we link_whole. But we still need to diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py index 32ff6af..00c7c7a 100644 --- a/mesonbuild/modules/qt.py +++ b/mesonbuild/modules/qt.py @@ -236,7 +236,7 @@ class QtBaseModule(ExtensionModule): if c.endswith('.qm'): ts_files.append(c.rstrip('.qm')+'.ts') else: - raise MesonException('qt.compile_translations: qresource can only contain ts files, found {}'.format(c)) + raise MesonException('qt.compile_translations: qresource can only contain qm files, found {}'.format(c)) results = self.preprocess(state, [], {'qresources': qresource, 'rcc_extra_arguments': kwargs.get('rcc_extra_arguments', [])}) self._detect_tools(state.environment, kwargs.get('method', 'auto')) translations = [] diff --git a/mesonbuild/msubprojects.py b/mesonbuild/msubprojects.py index 20639cb..8fb31f2 100755 --- a/mesonbuild/msubprojects.py +++ b/mesonbuild/msubprojects.py @@ -85,7 +85,7 @@ def git_reset(repo_dir, revision): return True def git_checkout(repo_dir, revision, create=False): - cmd = ['checkout', revision, '--'] + cmd = ['checkout', '--ignore-other-worktrees', revision, '--'] if create: cmd.insert('-b', 1) try: diff --git a/mesonbuild/scripts/depfixer.py b/mesonbuild/scripts/depfixer.py index 18d70cc..e31f87d 100644 --- a/mesonbuild/scripts/depfixer.py +++ b/mesonbuild/scripts/depfixer.py @@ -313,7 +313,7 @@ class Elf(DataSizes): # Only add each one once. new_rpaths = OrderedSet() # type: OrderedSet[bytes] if new_rpath: - new_rpaths.add(new_rpath) + new_rpaths.update(new_rpath.split(b':')) if old_rpath: # Filter out build-only rpath entries # added by get_link_dep_subdirs() or diff --git a/mesonbuild/wrap/wrap.py b/mesonbuild/wrap/wrap.py index bc35893..7b50bc1 100644 --- a/mesonbuild/wrap/wrap.py +++ b/mesonbuild/wrap/wrap.py @@ -31,7 +31,8 @@ import textwrap from .._pathlib import Path from . import WrapMode from .. import coredata -from ..mesonlib import verbose_git, quiet_git, GIT, ProgressBar, MesonException +from ..mesonlib import quiet_git, GIT, ProgressBar, MesonException +from .. import mesonlib if T.TYPE_CHECKING: import http.client @@ -186,6 +187,15 @@ def get_directory(subdir_root: str, packagename: str) -> str: return wrap.directory return packagename +def verbose_git(*args, **kwargs): + ''' + Wrapper to convert GitException to WrapException caught in interpreter. + ''' + try: + return mesonlib.verbose_git(*args, **kwargs) + except mesonlib.GitException as e: + raise WrapException(str(e)) + class Resolver: def __init__(self, source_dir: str, subdir: str, wrap_mode: WrapMode = WrapMode.default) -> None: self.source_dir = source_dir diff --git a/run_unittests.py b/run_unittests.py index c7c6ba2..8025256 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -124,15 +124,6 @@ def is_ci(): return True return False -def is_pull(): - # Travis - if os.environ.get('TRAVIS_PULL_REQUEST', 'false') != 'false': - return True - # Azure - if 'SYSTEM_PULLREQUEST_ISFORK' in os.environ: - return True - return False - def _git_init(project_dir): subprocess.check_call(['git', 'init'], cwd=project_dir, stdout=subprocess.DEVNULL) subprocess.check_call(['git', 'config', @@ -1448,43 +1439,6 @@ class DataTests(unittest.TestCase): res = re.search(r'syn keyword mesonBuiltin(\s+\\\s\w+)+', f.read(), re.MULTILINE) defined = set([a.strip() for a in res.group().split('\\')][1:]) self.assertEqual(defined, set(chain(interp.funcs.keys(), interp.builtin.keys()))) - - @unittest.skipIf(is_pull(), 'Skipping because this is a pull request') - def test_json_grammar_syntax_highlighting(self): - ''' - Ensure that syntax highlighting JSON grammar written by TingPing was - updated for new functions in the global namespace in build files. - https://github.com/TingPing/language-meson/ - ''' - env = get_fake_env() - interp = Interpreter(FakeBuild(env), mock=True) - url = 'https://raw.githubusercontent.com/TingPing/language-meson/master/grammars/meson.json' - try: - # Use a timeout to avoid blocking forever in case the network is - # slow or unavailable in a weird way - r = urllib.request.urlopen(url, timeout=URLOPEN_TIMEOUT) - except urllib.error.URLError as e: - # Skip test when network is not available, such as during packaging - # by a distro or Flatpak - if not isinstance(e, urllib.error.HTTPError): - raise unittest.SkipTest('Network unavailable') - # Don't fail the test if github is down, but do fail if 4xx - if e.code >= 500: - raise unittest.SkipTest('Server error ' + str(e.code)) - raise e - # On Python 3.5, we must decode bytes to string. Newer versions don't require that. - grammar = json.loads(r.read().decode('utf-8', 'surrogatepass')) - for each in grammar['patterns']: - if 'name' in each and each['name'] == 'support.function.builtin.meson': - # The string is of the form: (?x)\\b(func1|func2|...\n)\\b\\s*(?=\\() and - # we convert that to [func1, func2, ...] without using regex to parse regex - funcs = set(each['match'].split('\\b(')[1].split('\n')[0].split('|')) - if 'name' in each and each['name'] == 'support.variable.meson': - # \\b(builtin1|builtin2...)\\b - builtin = set(each['match'].split('\\b(')[1].split(')\\b')[0].split('|')) - self.assertEqual(builtin, set(interp.builtin.keys())) - self.assertEqual(funcs, set(interp.funcs.keys())) - def test_all_functions_defined_in_ast_interpreter(self): ''' Ensure that the all functions defined in the Interpreter are also defined @@ -2942,20 +2896,22 @@ class AllPlatformTests(BasePlatformTests): # the source tree leads to all kinds of trouble. with tempfile.TemporaryDirectory() as project_dir: with open(os.path.join(project_dir, 'meson.build'), 'w') as ofile: - ofile.write('''project('disttest', 'c', version : '1.4.3') -e = executable('distexe', 'distexe.c') -test('dist test', e) -subproject('vcssub', required : false) -subproject('tarballsub', required : false) -''') + ofile.write(textwrap.dedent('''\ + project('disttest', 'c', version : '1.4.3') + e = executable('distexe', 'distexe.c') + test('dist test', e) + subproject('vcssub', required : false) + subproject('tarballsub', required : false) + ''')) with open(os.path.join(project_dir, 'distexe.c'), 'w') as ofile: - ofile.write('''#include + ofile.write(textwrap.dedent('''\ + #include -int main(int argc, char **argv) { - printf("I am a distribution test.\\n"); - return 0; -} -''') + int main(int argc, char **argv) { + printf("I am a distribution test.\\n"); + return 0; + } + ''')) xz_distfile = os.path.join(self.distdir, 'disttest-1.4.3.tar.xz') xz_checksumfile = xz_distfile + '.sha256sum' zip_distfile = os.path.join(self.distdir, 'disttest-1.4.3.zip') @@ -3614,8 +3570,8 @@ int main(int argc, char **argv) { """ 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 = ('WARNING: target links against shared modules. This is not ' + 'recommended as it is not supported on some platforms') self.assertIn(msg, out) def test_ndebug_if_release_disabled(self): @@ -5247,6 +5203,20 @@ recommended as it is not supported on some platforms''') wrap = PackageDefinition(redirect_wrap) self.assertEqual(wrap.get('url'), 'http://invalid') + @skip_if_no_cmake + def test_nested_cmake_rebuild(self) -> None: + # This checks a bug where if a non-meson project is used as a third + # level (or deeper) subproject it doesn't cause a rebuild if the build + # files for that project are changed + testdir = os.path.join(self.unit_test_dir, '85 nested subproject regenerate depends') + cmakefile = Path(testdir) / 'subprojects' / 'sub2' / 'CMakeLists.txt' + self.init(testdir) + self.build() + with cmakefile.open('a') as f: + os.utime(str(cmakefile)) + self.assertReconfiguredBuildIsNoop() + + class FailureTests(BasePlatformTests): ''' Tests that test failure conditions. Build files here should be dynamically @@ -7250,16 +7220,18 @@ class LinuxlikeTests(BasePlatformTests): testdir = os.path.join(self.unit_test_dir, '61 identity cross') nativefile = tempfile.NamedTemporaryFile(mode='w') - nativefile.write('''[binaries] -c = ['{0}'] -'''.format(os.path.join(testdir, 'build_wrapper.py'))) + nativefile.write(textwrap.dedent('''\ + [binaries] + c = ['{0}'] + '''.format(os.path.join(testdir, 'build_wrapper.py')))) nativefile.flush() self.meson_native_file = nativefile.name crossfile = tempfile.NamedTemporaryFile(mode='w') - crossfile.write('''[binaries] -c = ['{0}'] -'''.format(os.path.join(testdir, 'host_wrapper.py'))) + crossfile.write(textwrap.dedent('''\ + [binaries] + c = ['{0}'] + '''.format(os.path.join(testdir, 'host_wrapper.py')))) crossfile.flush() self.meson_cross_file = crossfile.name @@ -7272,9 +7244,10 @@ c = ['{0}'] 'CC_FOR_BUILD': '"' + os.path.join(testdir, 'build_wrapper.py') + '"', } crossfile = tempfile.NamedTemporaryFile(mode='w') - crossfile.write('''[binaries] -c = ['{0}'] -'''.format(os.path.join(testdir, 'host_wrapper.py'))) + crossfile.write(textwrap.dedent('''\ + [binaries] + c = ['{0}'] + '''.format(os.path.join(testdir, 'host_wrapper.py')))) crossfile.flush() self.meson_cross_file = crossfile.name # TODO should someday be explicit about build platform only here @@ -7335,8 +7308,9 @@ c = ['{0}'] self._check_ld('ld.lld', 'lld', 'c', 'ld.lld') @skip_if_not_language('rust') + @skipIfNoExecutable('ld.gold') # need an additional check here because _check_ld checks for gcc def test_ld_environment_variable_rust(self): - self._check_ld('ld.gold', 'gold', 'rust', 'ld.gold') + self._check_ld('gcc', 'gcc -fuse-ld=gold', 'rust', 'ld.gold') def test_ld_environment_variable_cpp(self): self._check_ld('ld.gold', 'gold', 'cpp', 'ld.gold') diff --git a/test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt index 7fce89e..6258ca0 100644 --- a/test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt +++ b/test cases/cmake/2 advanced/subprojects/cmMod/CMakeLists.txt @@ -23,6 +23,16 @@ target_link_libraries(cmModLib ZLIB::ZLIB) target_link_libraries(cmModLibStatic ;ZLIB::ZLIB;) target_link_libraries(testEXE cmModLib) +if(APPLE) + find_library(COREFOUNDATION_FRAMEWORK "CoreFoundation") + if(NOT COREFOUNDATION_FRAMEWORK) + message(FATAL_ERROR "CoreFoundation framework not found") + endif() + + target_link_libraries(cmModLibStatic "${COREFOUNDATION_FRAMEWORK}") + target_compile_definitions(cmModLibStatic PUBLIC USE_FRAMEWORK) +endif() + target_compile_definitions(cmModLibStatic PUBLIC CMMODLIB_STATIC_DEFINE) install(TARGETS testEXE LIBRARY DESTINATION lib RUNTIME DESTINATION bin) diff --git a/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp b/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp index 027296e..eb41438 100644 --- a/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp +++ b/test cases/cmake/2 advanced/subprojects/cmMod/lib/cmMod.cpp @@ -6,10 +6,19 @@ #error "Invalid value of CONFIG_OPT" #endif +#ifdef USE_FRAMEWORK +#include +#endif + using namespace std; cmModClass::cmModClass(string foo) { str = foo + " World " + zlibVersion(); + +#ifdef USE_FRAMEWORK + CFStringRef ref = CFStringCreateWithCString(NULL, str.c_str(), kCFStringEncodingUTF8); + CFRelease(ref); +#endif } string cmModClass::getStr() const { diff --git a/test cases/cmake/24 mixing languages/main.c b/test cases/cmake/24 mixing languages/main.c new file mode 100644 index 0000000..028a78e --- /dev/null +++ b/test cases/cmake/24 mixing languages/main.c @@ -0,0 +1,5 @@ +#include + +int main(void) { + return doStuff(); +} diff --git a/test cases/cmake/24 mixing languages/meson.build b/test cases/cmake/24 mixing languages/meson.build new file mode 100644 index 0000000..4ab1d85 --- /dev/null +++ b/test cases/cmake/24 mixing languages/meson.build @@ -0,0 +1,13 @@ +project('CMake mix', ['c', 'cpp']) + +if not add_languages('objc', required : false) + error('MESON_SKIP_TEST: No ObjC compiler') +endif + +cm = import('cmake') + +sub_pro = cm.subproject('cmTest') +sub_dep = sub_pro.dependency('cmTest', include_type: 'system') + +exe1 = executable('exe1', ['main.c'], dependencies: [sub_dep]) +test('test1', exe1) diff --git a/test cases/cmake/24 mixing languages/subprojects/cmTest/CMakeLists.txt b/test cases/cmake/24 mixing languages/subprojects/cmTest/CMakeLists.txt new file mode 100644 index 0000000..80a256f --- /dev/null +++ b/test cases/cmake/24 mixing languages/subprojects/cmTest/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.5) + +project(cmTest) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +add_library(cmTest STATIC cmTest.c cmTest.m) +target_compile_definitions(cmTest PUBLIC SOME_MAGIC_DEFINE=42) diff --git a/test cases/cmake/24 mixing languages/subprojects/cmTest/cmTest.c b/test cases/cmake/24 mixing languages/subprojects/cmTest/cmTest.c new file mode 100644 index 0000000..066d676 --- /dev/null +++ b/test cases/cmake/24 mixing languages/subprojects/cmTest/cmTest.c @@ -0,0 +1,13 @@ +#include "cmTest.h" +#include + +#if SOME_MAGIC_DEFINE != 42 +#error "SOME_MAGIC_DEFINE != 42" +#endif + +int foo(int x); + +int doStuff(void) { + printf("Hello World\n"); + return foo(42); +} diff --git a/test cases/cmake/24 mixing languages/subprojects/cmTest/cmTest.h b/test cases/cmake/24 mixing languages/subprojects/cmTest/cmTest.h new file mode 100644 index 0000000..a6a5c24 --- /dev/null +++ b/test cases/cmake/24 mixing languages/subprojects/cmTest/cmTest.h @@ -0,0 +1,3 @@ +#pragma once + +int doStuff(void); diff --git a/test cases/cmake/24 mixing languages/subprojects/cmTest/cmTest.m b/test cases/cmake/24 mixing languages/subprojects/cmTest/cmTest.m new file mode 100644 index 0000000..16ec805 --- /dev/null +++ b/test cases/cmake/24 mixing languages/subprojects/cmTest/cmTest.m @@ -0,0 +1,7 @@ +#if SOME_MAGIC_DEFINE != 42 +#error "SOME_MAGIC_DEFINE != 42" +#endif + +int foo(int x) { + return 42 - x; +} diff --git a/test cases/common/122 llvm ir and assembly/square-aarch64.S b/test cases/common/122 llvm ir and assembly/square-aarch64.S index ebe74e7..02f1a12 100644 --- a/test cases/common/122 llvm ir and assembly/square-aarch64.S +++ b/test cases/common/122 llvm ir and assembly/square-aarch64.S @@ -15,6 +15,15 @@ SYMBOL_NAME(square_unsigned) ENDP #else -#error gas syntax assembly for this test needs to be written +.text +.globl SYMBOL_NAME(square_unsigned) +# ifdef __linux__ +.type square_unsigned, %function +#endif + +SYMBOL_NAME(square_unsigned): + mul x1, x0, x0 + mov x0, x1 + ret #endif diff --git a/test cases/failing/76 link with shared module on osx/test.json b/test cases/failing/76 link with shared module on osx/test.json index 4e2856f..0a32674 100644 --- a/test cases/failing/76 link with shared module on osx/test.json +++ b/test cases/failing/76 link with shared module on osx/test.json @@ -1,7 +1,7 @@ { "stdout": [ { - "line": "test cases/failing/76 link with shared module on osx/meson.build:8:0: ERROR: target links against shared modules." + "line": "test cases/failing/76 link with shared module on osx/meson.build:8:0: ERROR: target links against shared modules. This is not permitted on OSX" } ] } diff --git a/test cases/unit/85 nested subproject regenerate depends/main.c b/test cases/unit/85 nested subproject regenerate depends/main.c new file mode 100644 index 0000000..9b6bdc2 --- /dev/null +++ b/test cases/unit/85 nested subproject regenerate depends/main.c @@ -0,0 +1,3 @@ +int main(void) { + return 0; +} diff --git a/test cases/unit/85 nested subproject regenerate depends/meson.build b/test cases/unit/85 nested subproject regenerate depends/meson.build new file mode 100644 index 0000000..5698881 --- /dev/null +++ b/test cases/unit/85 nested subproject regenerate depends/meson.build @@ -0,0 +1,6 @@ +project('nested subproject regenerate depends', 'c') + +s = subproject('sub1') + +# This is needed to make msbuild noop check work correctly +executable('exe', 'main.c') diff --git a/test cases/unit/85 nested subproject regenerate depends/subprojects/sub1/meson.build b/test cases/unit/85 nested subproject regenerate depends/subprojects/sub1/meson.build new file mode 100644 index 0000000..a31db4a --- /dev/null +++ b/test cases/unit/85 nested subproject regenerate depends/subprojects/sub1/meson.build @@ -0,0 +1,4 @@ +project('sub1') + +cmake = import('cmake') +cmake.subproject('sub2') diff --git a/test cases/unit/85 nested subproject regenerate depends/subprojects/sub2/CMakeLists.txt b/test cases/unit/85 nested subproject regenerate depends/subprojects/sub2/CMakeLists.txt new file mode 100644 index 0000000..f91473a --- /dev/null +++ b/test cases/unit/85 nested subproject regenerate depends/subprojects/sub2/CMakeLists.txt @@ -0,0 +1 @@ +project(sub2) -- 2.7.4