diff options
45 files changed, 514 insertions, 162 deletions
diff --git a/ci/ciimage/bionic/install.sh b/ci/ciimage/bionic/install.sh index 74b407f..23e0774 100755 --- a/ci/ciimage/bionic/install.sh +++ b/ci/ciimage/bionic/install.sh @@ -27,6 +27,7 @@ pkgs=( libblocksruntime-dev libperl-dev libscalapack-mpi-dev libncurses-dev itstool + openjdk-11-jre ) boost_pkgs=(atomic chrono date-time filesystem log regex serialization system test thread) diff --git a/ci/ciimage/ubuntu-rolling/install.sh b/ci/ciimage/ubuntu-rolling/install.sh index aa73cbc..550d311 100755 --- a/ci/ciimage/ubuntu-rolling/install.sh +++ b/ci/ciimage/ubuntu-rolling/install.sh @@ -26,6 +26,7 @@ pkgs=( liblapack-dev libscalapack-mpi-dev bindgen itstool + openjdk-11-jre ) sed -i '/^#\sdeb-src /s/^#//' "/etc/apt/sources.list" diff --git a/docs/markdown/Dependencies.md b/docs/markdown/Dependencies.md index a7e34e5..8165955 100644 --- a/docs/markdown/Dependencies.md +++ b/docs/markdown/Dependencies.md @@ -494,23 +494,34 @@ Deprecated name for JNI. `dependency('jdk')` instead of `dependency('jni')`. *(added 0.62.0)* +`modules` is an optional list of strings containing any of `jvm` and `awt`. + Provides access to compiling with the Java Native Interface (JNI). The lookup will first check if `JAVA_HOME` is set in the environment, and if not will use the resolved path of `javac`. Systems will usually link your preferred JDK to well known paths like `/usr/bin/javac` on Linux for instance. Using the path from `JAVA_HOME` or the resolved `javac`, this dependency will place the JDK installation's `include` directory and its platform-dependent subdirectory on -the compiler's include path. +the compiler's include path. If `modules` is non-empty, then the proper linker +arguments will also be added. ```meson -dep = dependency('jni', version: '>= 1.8.0') +dep = dependency('jni', version: '>= 1.8.0', modules: ['jvm']) ``` **Note**: Due to usage of a resolved path, upgrading the JDK may cause the -various headers to not be found. In that case, please reconfigure the build +various paths to not be found. In that case, please reconfigure the build directory. One workaround is to explicitly set `JAVA_HOME` instead of relying on the fallback `javac` resolved path behavior. +**Note**: Include paths might be broken on platforms other than `linux`, +`windows`, `darwin`, and `sunos`. Please submit a PR or open an issue in this +case. + +**Note**: Use of the `modules` argument on a JDK `<= 1.8` may be broken if your +system is anything other than `x86_64`. Please submit a PR or open an issue in +this case. + ## libgcrypt *(added 0.49.0)* diff --git a/docs/markdown/Wayland-module.md b/docs/markdown/Wayland-module.md index d30627c..345c4e3 100644 --- a/docs/markdown/Wayland-module.md +++ b/docs/markdown/Wayland-module.md @@ -26,7 +26,6 @@ executable('hw', 'main.c', xdg_shell, dependencies : wl_dep) ## Methods ### find_protocol - ```meson xml = wl_mod.find_protocol( 'xdg-decoration', @@ -35,28 +34,37 @@ xml = wl_mod.find_protocol( ) ``` This function requires one positional argument: the protocol base name. + +It takes the following keyword arguments: - `state` Optional arg that specifies the current state of the protocol. -Either stable, staging, or unstable. -The default is stable. -- `version` The backwards incompatible version number. -Required for staging or unstable. An error is raised for stable. + Either `'stable'`, `'staging'`, or `'unstable'`. The default is `'stable'`. +- `version` The backwards incompatible version number as integer. + Required for staging and unstable, but not allowed for stable. + +**Returns**: a [[@file]] that can be passed to [scan_xml](#scan_xml) ### scan_xml ```meson generated = wl_mod.scan_xml( 'my-protocol.xml', - side : 'client', - scope : 'private', + client : true, + server : true, + public : false, ) ``` This function accepts one or more arguments of either string or file type. -- `side` Optional arg that specifies if client or server side code is generated. -The default is client side. -- `scope` Optional arg that specifies the scope of the generated code. -Either public or private. -The default is private. +It takes the following keyword arguments: +- `public` Optional arg that specifies the scope of the generated code. + The default is false. +- `client` Optional arg that specifies if client side header file is + generated. The default is true. +- `server` Optional arg that specifies if server side header file is + generated. The default is false. +**Returns**: a list of [[@custom_tgt]] in the order source, client side header, +server side header. Generated header files have the name +`<name>-<client|server>-protocol.h`. ## Links - [Official Wayland Documentation](https://wayland.freedesktop.org/docs/html/) diff --git a/docs/markdown/howtox.md b/docs/markdown/howtox.md index 91098aa..9945a6c 100644 --- a/docs/markdown/howtox.md +++ b/docs/markdown/howtox.md @@ -141,7 +141,7 @@ configure_file(...) ## Generate configuration data from files -`The [fs module](#Fs-modules) offers the `read` function` which enables adding +The [fs module](#Fs-modules) offers the `read` function which enables adding the contents of arbitrary files to configuration data (among other uses): ```meson diff --git a/docs/markdown/snippets/find_program-version.md b/docs/markdown/snippets/find_program-version.md new file mode 100644 index 0000000..fe3a9a6 --- /dev/null +++ b/docs/markdown/snippets/find_program-version.md @@ -0,0 +1,5 @@ +## found programs now have a version method + +The return value of [[find_program]] can now check the exact version of the +found program, independent of the minimum version requirement. This can be used +e.g. to perform different actions depending on the exact version detected. diff --git a/docs/markdown/snippets/jni-system-dep-modules.md b/docs/markdown/snippets/jni-system-dep-modules.md new file mode 100644 index 0000000..1f2c567 --- /dev/null +++ b/docs/markdown/snippets/jni-system-dep-modules.md @@ -0,0 +1,10 @@ +## JNI System Dependency Modules + +The JNI system dependency now supports a `modules` keyword argument which is a +list containing any of the following: `jvm`, `awt`. + +```meson +jni_dep = dependency('jni', version: '>= 1.8.0', modules: ['jvm', 'awt']) +``` + +This will add appropriate linker arguments to your target. diff --git a/docs/markdown/snippets/merge_file_sanity.md b/docs/markdown/snippets/merge_file_sanity.md new file mode 100644 index 0000000..ff59c51 --- /dev/null +++ b/docs/markdown/snippets/merge_file_sanity.md @@ -0,0 +1,15 @@ +## i18n.merge_file no longer arbitrarily leaves your project half-built + +The i18n module partially accounts for builds with NLS disabled, by disabling +gettext compiled translation catalogs if it cannot build them. Due to +implementation details, this also disabled important data files created via +merge_file, leading to important desktop files etc. not being installed. + +This overreaction has been fixed. It is no longer possible to have NLS-disabled +builds which break the project by not installing important files which have +nothing to do with NLS (other than including some). + +If you were depending on not having the Gettext tools installed and +successfully mis-building your project, you may need to make your project +actually work with NLS disabled, for example by providing some version of your +files which is still installed even when merge_file cannot be run. diff --git a/docs/yaml/objects/external_program.yaml b/docs/yaml/objects/external_program.yaml index 02bf48f..f406477 100644 --- a/docs/yaml/objects/external_program.yaml +++ b/docs/yaml/objects/external_program.yaml @@ -24,6 +24,14 @@ methods: run_command(find_program('foo'), 'arg1', 'arg2') ``` +- name: version + returns: str + since: 0.62.0 + description: | + The version number as a string, for example `1.2.8`. + + `unknown` if the program cannot determine the version via a `--version` argument. + - name: full_path returns: str since: 0.55.0 diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index dae9c47..b98eb08 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -135,7 +135,6 @@ class TargetInstallData: fname: str outdir: str outdir_name: InitVar[str] - aliases: T.Dict[str, str] strip: bool install_name_mappings: T.Mapping[str, str] rpath_dirs_to_remove: T.Set[bytes] @@ -173,6 +172,7 @@ class InstallSymlinkData: install_path: str subproject: str tag: T.Optional[str] = None + allow_missing: bool = False # cannot use dataclass here because "exclude" is out of order class SubdirInstallData(InstallDataBase): @@ -1581,12 +1581,17 @@ class Backend: tag = t.install_tag[0] or ('devel' if isinstance(t, build.StaticLibrary) else 'runtime') mappings = t.get_link_deps_mapping(d.prefix, self.environment) i = TargetInstallData(self.get_target_filename(t), outdirs[0], - install_dir_name, t.get_aliases(), + install_dir_name, should_strip, mappings, t.rpath_dirs_to_remove, t.install_rpath, install_mode, t.subproject, tag=tag) d.targets.append(i) + for alias, to, tag in t.get_aliases(): + alias = os.path.join(outdirs[0], alias) + s = InstallSymlinkData(to, alias, outdirs[0], t.subproject, tag, allow_missing=True) + d.symlinks.append(s) + if isinstance(t, (build.SharedLibrary, build.SharedModule, build.Executable)): # On toolchains/platforms that use an import library for # linking (separate from the shared library with all the @@ -1602,7 +1607,7 @@ class Backend: # Install the import library; may not exist for shared modules i = TargetInstallData(self.get_target_filename_for_linking(t), implib_install_dir, install_dir_name, - {}, False, {}, set(), '', install_mode, + False, {}, set(), '', install_mode, t.subproject, optional=isinstance(t, build.SharedModule), tag='devel') d.targets.append(i) @@ -1611,7 +1616,7 @@ class Backend: debug_file = os.path.join(self.get_target_dir(t), t.get_debug_filename()) i = TargetInstallData(debug_file, outdirs[0], install_dir_name, - {}, False, {}, set(), '', + False, {}, set(), '', install_mode, t.subproject, optional=True, tag='devel') d.targets.append(i) @@ -1622,7 +1627,7 @@ class Backend: if outdir is False: continue f = os.path.join(self.get_target_dir(t), output) - i = TargetInstallData(f, outdir, install_dir_name, {}, False, {}, set(), None, + i = TargetInstallData(f, outdir, install_dir_name, False, {}, set(), None, install_mode, t.subproject, tag=tag) d.targets.append(i) @@ -1639,7 +1644,7 @@ class Backend: f = os.path.join(self.get_target_dir(t), output) if not install_dir_name: dir_name = os.path.join('{prefix}', outdirs[0]) - i = TargetInstallData(f, outdirs[0], dir_name, {}, + i = TargetInstallData(f, outdirs[0], dir_name, False, {}, set(), None, install_mode, t.subproject, optional=not t.build_by_default, tag=tag) @@ -1653,7 +1658,7 @@ class Backend: if not install_dir_name: dir_name = os.path.join('{prefix}', outdir) i = TargetInstallData(f, outdir, dir_name, - {}, False, {}, set(), None, install_mode, + False, {}, set(), None, install_mode, t.subproject, optional=not t.build_by_default, tag=tag) d.targets.append(i) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 560fe53..a66111b 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -3092,8 +3092,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) return self.get_target_filename(t) def generate_shlib_aliases(self, target, outdir): - aliases = target.get_aliases() - for alias, to in aliases.items(): + for alias, to, tag in target.get_aliases(): aliasfile = os.path.join(self.environment.get_build_dir(), outdir, alias) try: os.remove(aliasfile) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index bd6661b..6916bcb 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -1471,8 +1471,8 @@ You probably should put it in link_with instead.''') else: self.extra_args[language] = args - def get_aliases(self) -> T.Dict[str, str]: - return {} + def get_aliases(self) -> T.List[T.Tuple[str, str, str]]: + return [] def get_langs_used_by_deps(self) -> T.List[str]: ''' @@ -2240,7 +2240,7 @@ class SharedLibrary(BuildTarget): def get_all_link_deps(self): return [self] + self.get_transitive_link_deps() - def get_aliases(self) -> T.Dict[str, str]: + def get_aliases(self) -> T.List[T.Tuple[str, str, str]]: """ If the versioned library name is libfoo.so.0.100.0, aliases are: * libfoo.so.0 (soversion) -> libfoo.so.0.100.0 @@ -2248,7 +2248,7 @@ class SharedLibrary(BuildTarget): Same for dylib: * libfoo.dylib (unversioned; for linking) -> libfoo.0.dylib """ - aliases: T.Dict[str, str] = {} + aliases: T.List[T.Tuple[str, str, str]] = [] # Aliases are only useful with .so and .dylib libraries. Also if # there's no self.soversion (no versioning), we don't need aliases. if self.suffix not in ('so', 'dylib') or not self.soversion: @@ -2260,14 +2260,16 @@ class SharedLibrary(BuildTarget): if self.suffix == 'so' and self.ltversion and self.ltversion != self.soversion: alias_tpl = self.filename_tpl.replace('ltversion', 'soversion') ltversion_filename = alias_tpl.format(self) - aliases[ltversion_filename] = self.filename + tag = self.install_tag[0] or 'runtime' + aliases.append((ltversion_filename, self.filename, tag)) # libfoo.so.0/libfoo.0.dylib is the actual library else: ltversion_filename = self.filename # Unversioned alias: # libfoo.so -> libfoo.so.0 # libfoo.dylib -> libfoo.0.dylib - aliases[self.basic_filename_tpl.format(self)] = ltversion_filename + tag = self.install_tag[0] or 'devel' + aliases.append((self.basic_filename_tpl.format(self), ltversion_filename, tag)) return aliases def type_suffix(self): diff --git a/mesonbuild/cmake/generator.py b/mesonbuild/cmake/generator.py index 848fdf9..b68778d 100644 --- a/mesonbuild/cmake/generator.py +++ b/mesonbuild/cmake/generator.py @@ -13,9 +13,18 @@ # limitations under the License. from .. import mesonlib +from .common import cmake_is_debug import typing as T -def parse_generator_expressions(raw: str) -> str: +if T.TYPE_CHECKING: + from .traceparser import CMakeTraceParser, CMakeTarget + +def parse_generator_expressions( + raw: str, + trace: 'CMakeTraceParser', + *, + context_tgt: T.Optional['CMakeTarget'] = None, + ) -> str: '''Parse CMake generator expressions Most generator expressions are simply ignored for @@ -44,6 +53,48 @@ def parse_generator_expressions(raw: str) -> str: else: return '1' if mesonlib.version_compare(arg[:col_pos], '{}{}'.format(op, arg[col_pos + 1:])) else '0' + def target_property(arg: str) -> str: + # We can't really support this since we don't have any context + if ',' not in arg: + if context_tgt is None: + return '' + return ';'.join(context_tgt.properties.get(arg, [])) + + args = arg.split(',') + props = trace.targets[args[0]].properties.get(args[1], []) if args[0] in trace.targets else [] + return ';'.join(props) + + def target_file(arg: str) -> str: + if arg not in trace.targets: + return '' + tgt = trace.targets[arg] + + cfgs = [] + cfg = '' + + if 'IMPORTED_CONFIGURATIONS' in tgt.properties: + cfgs = [x for x in tgt.properties['IMPORTED_CONFIGURATIONS'] if x] + cfg = cfgs[0] + + if cmake_is_debug(trace.env): + if 'DEBUG' in cfgs: + cfg = 'DEBUG' + elif 'RELEASE' in cfgs: + cfg = 'RELEASE' + else: + if 'RELEASE' in cfgs: + cfg = 'RELEASE' + + if f'IMPORTED_IMPLIB_{cfg}' in tgt.properties: + return ';'.join([x for x in tgt.properties[f'IMPORTED_IMPLIB_{cfg}'] if x]) + elif 'IMPORTED_IMPLIB' in tgt.properties: + return ';'.join([x for x in tgt.properties['IMPORTED_IMPLIB'] if x]) + elif f'IMPORTED_LOCATION_{cfg}' in tgt.properties: + return ';'.join([x for x in tgt.properties[f'IMPORTED_LOCATION_{cfg}'] if x]) + elif 'IMPORTED_LOCATION' in tgt.properties: + return ';'.join([x for x in tgt.properties['IMPORTED_LOCATION'] if x]) + return '' + supported = { # Boolean functions 'BOOL': lambda x: '0' if x.upper() in ['0', 'FALSE', 'OFF', 'N', 'NO', 'IGNORE', 'NOTFOUND'] or x.endswith('-NOTFOUND') else '1', @@ -51,6 +102,8 @@ def parse_generator_expressions(raw: str) -> str: 'OR': lambda x: '1' if any([y == '1' for y in x.split(',')]) else '0', 'NOT': lambda x: '0' if x == '1' else '1', + 'IF': lambda x: x.split(',')[1] if x.split(',')[0] == '1' else x.split(',')[2], + '0': lambda x: '', '1': lambda x: x, @@ -78,6 +131,12 @@ def parse_generator_expressions(raw: str) -> str: 'ANGLE-R': lambda x: '>', 'COMMA': lambda x: ',', 'SEMICOLON': lambda x: ';', + + # Target related expressions + 'TARGET_EXISTS': lambda x: '1' if x in trace.targets else '0', + 'TARGET_NAME_IF_EXISTS': lambda x: x if x in trace.targets else '', + 'TARGET_PROPERTY': target_property, + 'TARGET_FILE': target_file, } # type: T.Dict[str, T.Callable[[str], str]] # Recursively evaluate generator expressions diff --git a/mesonbuild/cmake/interpreter.py b/mesonbuild/cmake/interpreter.py index 44b7e57..a619d1a 100644 --- a/mesonbuild/cmake/interpreter.py +++ b/mesonbuild/cmake/interpreter.py @@ -784,7 +784,7 @@ class CMakeInterpreter: self.languages = [] # type: T.List[str] self.targets = [] # type: T.List[ConverterTarget] self.custom_targets = [] # type: T.List[ConverterCustomTarget] - self.trace = CMakeTraceParser('', Path('.')) # Will be replaced in analyse + self.trace = CMakeTraceParser('', Path('.'), self.env) # Will be replaced in analyse self.output_target_map = OutputTargetMap(self.build_dir) # Generated meson data @@ -805,7 +805,7 @@ class CMakeInterpreter: cmake_exe = CMakeExecutor(self.env, '>=3.14', MachineChoice.BUILD) if not cmake_exe.found(): raise CMakeException('Unable to find CMake') - self.trace = CMakeTraceParser(cmake_exe.version(), self.build_dir, permissive=True) + self.trace = CMakeTraceParser(cmake_exe.version(), self.build_dir, self.env, permissive=True) preload_file = DataFile('cmake/data/preload.cmake').write_to_private(self.env) toolchain = CMakeToolchain(cmake_exe, self.env, self.for_machine, CMakeExecScope.SUBPROJECT, self.build_dir, preload_file) diff --git a/mesonbuild/cmake/toolchain.py b/mesonbuild/cmake/toolchain.py index 316f57c..e30f642 100644 --- a/mesonbuild/cmake/toolchain.py +++ b/mesonbuild/cmake/toolchain.py @@ -232,7 +232,7 @@ class CMakeToolchain: temp_toolchain_file.write_text(CMakeToolchain._print_vars(self.variables), encoding='utf-8') # Configure - trace = CMakeTraceParser(self.cmakebin.version(), build_dir) + trace = CMakeTraceParser(self.cmakebin.version(), build_dir, self.env) self.cmakebin.set_exec_mode(print_cmout=False, always_capture_stderr=trace.requires_stderr()) cmake_args = [] cmake_args += trace.trace_args() diff --git a/mesonbuild/cmake/traceparser.py b/mesonbuild/cmake/traceparser.py index 7909e13..7336d15 100644 --- a/mesonbuild/cmake/traceparser.py +++ b/mesonbuild/cmake/traceparser.py @@ -27,6 +27,9 @@ import re import json import textwrap +if T.TYPE_CHECKING: + from ..environment import Environment + class CMakeTraceLine: def __init__(self, file_str: str, line: int, func: str, args: T.List[str]) -> None: self.file = CMakeTraceLine._to_path(file_str) @@ -90,7 +93,7 @@ class CMakeGeneratorTarget(CMakeTarget): self.working_dir = None # type: T.Optional[Path] class CMakeTraceParser: - def __init__(self, cmake_version: str, build_dir: Path, permissive: bool = True) -> None: + def __init__(self, cmake_version: str, build_dir: Path, env: 'Environment', permissive: bool = True) -> None: self.vars: T.Dict[str, T.List[str]] = {} self.vars_by_file: T.Dict[Path, T.Dict[str, T.List[str]]] = {} self.targets: T.Dict[str, CMakeTarget] = {} @@ -101,6 +104,7 @@ class CMakeTraceParser: # T.List of targes that were added with add_custom_command to generate files self.custom_targets = [] # type: T.List[CMakeGeneratorTarget] + self.env = env self.permissive = permissive # type: bool self.cmake_version = cmake_version # type: str self.trace_file = 'cmake_trace.txt' @@ -194,6 +198,38 @@ class CMakeTraceParser: if fn: fn(l) + # Evaluate generator expressions + strlist_gen: T.Callable[[T.List[str]], T.List[str]] = lambda strlist: [parse_generator_expressions(x, self) for x in strlist] + pathlist_gen: T.Callable[[T.List[Path]], T.List[Path]] = lambda plist: [Path(parse_generator_expressions(str(x), self)) for x in plist] + + self.vars = {k: strlist_gen(v) for k, v in self.vars.items()} + self.vars_by_file = { + p: {k: strlist_gen(v) for k, v in d.items()} + for p, d in self.vars_by_file.items() + } + self.explicit_headers = set(Path(parse_generator_expressions(str(x), self)) for x in self.explicit_headers) + self.cache = { + k: CMakeCacheEntry( + strlist_gen(v.value), + v.type + ) + for k, v in self.cache.items() + } + + for tgt in self.targets.values(): + tgtlist_gen: T.Callable[[T.List[str], CMakeTarget], T.List[str]] = lambda strlist, t: [parse_generator_expressions(x, self, context_tgt=t) for x in strlist] + tgt.name = parse_generator_expressions(tgt.name, self, context_tgt=tgt) + tgt.type = parse_generator_expressions(tgt.type, self, context_tgt=tgt) + tgt.properties = { + k: tgtlist_gen(v, tgt) for k, v in tgt.properties.items() + } if tgt.properties is not None else None + tgt.depends = tgtlist_gen(tgt.depends, tgt) + + for ctgt in self.custom_targets: + ctgt.outputs = pathlist_gen(ctgt.outputs) + ctgt.command = [strlist_gen(x) for x in ctgt.command] + ctgt.working_dir = Path(parse_generator_expressions(str(ctgt.working_dir), self)) if ctgt.working_dir is not None else None + # Postprocess for tgt in self.targets.values(): tgt.strip_properties() @@ -688,7 +724,6 @@ class CMakeTraceParser: line = mo_file_line.group(3) func = mo_file_line.group(4) args = mo_file_line.group(5) - args = parse_generator_expressions(args) argl = args.split(' ') argl = list(map(lambda x: x.strip(), argl)) @@ -706,7 +741,6 @@ class CMakeTraceParser: args = data['args'] for j in args: assert isinstance(j, str) - args = [parse_generator_expressions(x) for x in args] yield CMakeTraceLine(data['file'], data['line'], data['cmd'], args) def _flatten_args(self, args: T.List[str]) -> T.List[str]: diff --git a/mesonbuild/dependencies/cmake.py b/mesonbuild/dependencies/cmake.py index 09f7f14..cff0e58 100644 --- a/mesonbuild/dependencies/cmake.py +++ b/mesonbuild/dependencies/cmake.py @@ -127,7 +127,7 @@ class CMakeDependency(ExternalDependency): return # Setup the trace parser - self.traceparser = CMakeTraceParser(self.cmakebin.version(), self._get_build_dir()) + self.traceparser = CMakeTraceParser(self.cmakebin.version(), self._get_build_dir(), self.env) cm_args = stringlistify(extract_as_list(kwargs, 'cmake_args')) cm_args = check_cmake_args(cm_args) @@ -166,7 +166,7 @@ class CMakeDependency(ExternalDependency): gen_list += [CMakeDependency.class_working_generator] gen_list += CMakeDependency.class_cmake_generators - temp_parser = CMakeTraceParser(self.cmakebin.version(), self._get_build_dir()) + temp_parser = CMakeTraceParser(self.cmakebin.version(), self._get_build_dir(), self.env) toolchain = CMakeToolchain(self.cmakebin, self.env, self.for_machine, CMakeExecScope.DEPENDENCY, self._get_build_dir()) toolchain.write() diff --git a/mesonbuild/dependencies/dev.py b/mesonbuild/dependencies/dev.py index 247dbc7..793b38a 100644 --- a/mesonbuild/dependencies/dev.py +++ b/mesonbuild/dependencies/dev.py @@ -15,6 +15,8 @@ # This file contains the detection logic for external dependencies useful for # development purposes, such as testing, debugging, etc.. +from __future__ import annotations + import glob import os import re @@ -38,6 +40,13 @@ from .pkgconfig import PkgConfigDependency if T.TYPE_CHECKING: from ..envconfig import MachineInfo from .. environment import Environment + from typing_extensions import TypedDict + + class JNISystemDependencyKW(TypedDict): + modules: T.List[str] + # FIXME: When dependency() moves to typed Kwargs, this should inherit + # from its TypedDict type. + version: T.Optional[str] def get_shared_library_suffix(environment: 'Environment', for_machine: MachineChoice) -> str: @@ -500,8 +509,8 @@ class ZlibSystemDependency(SystemDependency): class JNISystemDependency(SystemDependency): - def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]): - super().__init__('jni', environment, kwargs) + def __init__(self, environment: 'Environment', kwargs: JNISystemDependencyKW): + super().__init__('jni', environment, T.cast(T.Dict[str, T.Any], kwargs)) self.feature_since = ('0.62.0', '') @@ -512,6 +521,14 @@ class JNISystemDependency(SystemDependency): self.javac = environment.coredata.compilers[self.for_machine]['java'] self.version = self.javac.version + modules: T.List[str] = mesonlib.listify(kwargs.get('modules', [])) + for module in modules: + if module not in {'jvm', 'awt'}: + log = mlog.error if self.required else mlog.debug + log(f'Unknown JNI module ({module})') + self.is_found = False + return + if 'version' in kwargs and not version_compare(self.version, kwargs['version']): mlog.error(f'Incorrect JDK version found ({self.version}), wanted {kwargs["version"]}') self.is_found = False @@ -530,6 +547,44 @@ class JNISystemDependency(SystemDependency): java_home_include = self.java_home / 'include' self.compile_args.append(f'-I{java_home_include}') self.compile_args.append(f'-I{java_home_include / platform_include_dir}') + + if modules: + if m.is_windows(): + java_home_lib = self.java_home / 'lib' + java_home_lib_server = java_home_lib + else: + if version_compare(self.version, '<= 1.8.0'): + # The JDK and Meson have a disagreement here, so translate it + # over. In the event more translation needs to be done, add to + # following dict. + def cpu_translate(cpu: str) -> str: + java_cpus = { + 'x86_64': 'amd64', + } + + return java_cpus.get(cpu, cpu) + + java_home_lib = self.java_home / 'jre' / 'lib' / cpu_translate(m.cpu_family) + java_home_lib_server = java_home_lib / "server" + else: + java_home_lib = self.java_home / 'lib' + java_home_lib_server = java_home_lib / "server" + + if 'jvm' in modules: + jvm = self.clib_compiler.find_library('jvm', environment, extra_dirs=[str(java_home_lib_server)]) + if jvm is None: + mlog.debug('jvm library not found.') + self.is_found = False + else: + self.link_args.extend(jvm) + if 'awt' in modules: + jawt = self.clib_compiler.find_library('jawt', environment, extra_dirs=[str(java_home_lib)]) + if jawt is None: + mlog.debug('jawt library not found.') + self.is_found = False + else: + self.link_args.extend(jawt) + self.is_found = True @staticmethod @@ -553,7 +608,7 @@ class JNISystemDependency(SystemDependency): class JDKSystemDependency(JNISystemDependency): - def __init__(self, environment: 'Environment', kwargs: T.Dict[str, T.Any]): + def __init__(self, environment: 'Environment', kwargs: JNISystemDependencyKW): super().__init__(environment, kwargs) self.feature_since = ('0.59.0', '') diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index a3c0bef..ef899e2 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -518,6 +518,7 @@ class ExternalProgramHolder(ObjectHolder[ExternalProgram]): super().__init__(ep, interpreter) self.methods.update({'found': self.found_method, 'path': self.path_method, + 'version': self.version_method, 'full_path': self.full_path_method}) @noPosargs @@ -545,6 +546,17 @@ class ExternalProgramHolder(ObjectHolder[ExternalProgram]): assert path is not None return path + @noPosargs + @noKwargs + @FeatureNew('ExternalProgram.version', '0.62.0') + def version_method(self, args: T.List[TYPE_var], kwargs: TYPE_kwargs) -> str: + if not self.found(): + raise InterpreterException('Unable to get the version of a not-found external program') + try: + return self.held_object.get_version(self.interpreter) + except MesonException: + return 'unknown' + def found(self) -> bool: return self.held_object.found() diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py index 80b0239..b51d4f0 100644 --- a/mesonbuild/minstall.py +++ b/mesonbuild/minstall.py @@ -428,11 +428,11 @@ class Installer: append_to_log(self.lf, to_file) return True - def do_symlink(self, target: str, link: str, full_dst_dir: str) -> bool: + def do_symlink(self, target: str, link: str, full_dst_dir: str, allow_missing: bool) -> bool: abs_target = target if not os.path.isabs(target): abs_target = os.path.join(full_dst_dir, target) - if not os.path.exists(abs_target): + if not os.path.exists(abs_target) and not allow_missing: raise MesonException(f'Tried to install symlink to missing file {abs_target}') if os.path.exists(link): if not os.path.islink(link): @@ -592,7 +592,7 @@ class Installer: full_dst_dir = get_destdir_path(destdir, fullprefix, s.install_path) full_link_name = get_destdir_path(destdir, fullprefix, s.name) dm.makedirs(full_dst_dir, exist_ok=True) - if self.do_symlink(s.target, full_link_name, full_dst_dir): + if self.do_symlink(s.target, full_link_name, full_dst_dir, s.allow_missing): self.did_install_something = True def install_man(self, d: InstallData, dm: DirMaker, destdir: str, fullprefix: str) -> None: @@ -676,7 +676,6 @@ class Installer: outdir = get_destdir_path(destdir, fullprefix, t.outdir) outname = os.path.join(outdir, os.path.basename(fname)) final_path = os.path.join(d.prefix, t.outdir, os.path.basename(fname)) - aliases = t.aliases should_strip = t.strip install_rpath = t.install_rpath install_name_mappings = t.install_name_mappings @@ -711,9 +710,6 @@ class Installer: self.do_copydir(d, fname, outname, None, install_mode, dm) else: raise RuntimeError(f'Unknown file type for {fname!r}') - for alias, target in aliases.items(): - symlinkfilename = os.path.join(outdir, alias) - self.do_symlink(target, symlinkfilename, outdir) if file_copied: self.did_install_something = True try: diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py index 2ec7274..7b4f8aa 100644 --- a/mesonbuild/mintro.py +++ b/mesonbuild/mintro.py @@ -107,9 +107,6 @@ def list_installed(installdata: backends.InstallData) -> T.Dict[str, str]: for t in installdata.targets: res[os.path.join(installdata.build_dir, t.fname)] = \ os.path.join(installdata.prefix, t.outdir, os.path.basename(t.fname)) - for alias in t.aliases.keys(): - res[os.path.join(installdata.build_dir, alias)] = \ - os.path.join(installdata.prefix, t.outdir, os.path.basename(alias)) for i in installdata.data: res[i.path] = os.path.join(installdata.prefix, i.install_path) for i in installdata.headers: @@ -118,6 +115,9 @@ def list_installed(installdata: backends.InstallData) -> T.Dict[str, str]: res[i.path] = os.path.join(installdata.prefix, i.install_path) for i in installdata.install_subdirs: res[i.path] = os.path.join(installdata.prefix, i.install_path) + for s in installdata.symlinks: + basename = os.path.basename(s.name) + res[basename] = os.path.join(installdata.prefix, s.install_path, basename) return res def list_install_plan(installdata: backends.InstallData) -> T.Dict[str, T.Dict[str, T.Dict[str, T.Optional[str]]]]: @@ -216,9 +216,17 @@ def list_targets(builddata: build.Build, installdata: backends.InstallData, back # Fast lookup table for installation files install_lookuptable = {} for i in installdata.targets: - out = [os.path.join(installdata.prefix, i.outdir, os.path.basename(i.fname))] - out += [os.path.join(installdata.prefix, i.outdir, os.path.basename(x)) for x in i.aliases] - install_lookuptable[os.path.basename(i.fname)] = [str(PurePath(x)) for x in out] + basename = os.path.basename(i.fname) + install_lookuptable[basename] = [str(PurePath(installdata.prefix, i.outdir, basename))] + for s in installdata.symlinks: + # Symlink's target must already be in the table. They share the same list + # to support symlinks to symlinks recursively, such as .so -> .so.0 -> .so.1.2.3 + basename = os.path.basename(s.name) + try: + install_lookuptable[basename] = install_lookuptable[os.path.basename(s.target)] + install_lookuptable[basename].append(str(PurePath(installdata.prefix, s.install_path, basename))) + except KeyError: + pass for (idname, target) in builddata.get_targets().items(): if not isinstance(target, build.Target): diff --git a/mesonbuild/modules/i18n.py b/mesonbuild/modules/i18n.py index 98070d7..09703bb 100644 --- a/mesonbuild/modules/i18n.py +++ b/mesonbuild/modules/i18n.py @@ -11,9 +11,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations from os import path -import shutil import typing as T from . import ExtensionModule, ModuleReturnValue @@ -32,6 +32,7 @@ if T.TYPE_CHECKING: from ..build import Target from ..interpreter import Interpreter from ..interpreterbase import TYPE_var + from ..mparser import BaseNode from ..programs import ExternalProgram class MergeFile(TypedDict): @@ -131,14 +132,17 @@ class I18nModule(ExtensionModule): 'gettext': self.gettext, 'itstool_join': self.itstool_join, }) + self.tools: T.Dict[str, T.Optional[ExternalProgram]] = { + 'itstool': None, + 'msgfmt': None, + 'msginit': None, + 'msgmerge': None, + 'xgettext': None, + } @staticmethod - def nogettext_warning() -> None: - mlog.warning('Gettext not found, all translation targets will be ignored.', once=True) - - @staticmethod - def noitstool_error() -> T.NoReturn: - raise mesonlib.MesonException('Did not find itstool. Please install it to continue.') + def nogettext_warning(location: BaseNode) -> None: + mlog.warning('Gettext not found, all translation targets will be ignored.', once=True, location=location) @staticmethod def _get_data_dirs(state: 'ModuleState', dirs: T.Iterable[str]) -> T.List[str]: @@ -162,9 +166,8 @@ class I18nModule(ExtensionModule): KwargInfo('type', str, default='xml', validator=in_set_validator({'xml', 'desktop'})), ) def merge_file(self, state: 'ModuleState', args: T.List['TYPE_var'], kwargs: 'MergeFile') -> ModuleReturnValue: - if not shutil.which('xgettext'): - self.nogettext_warning() - return ModuleReturnValue(None, []) + if self.tools['msgfmt'] is None: + self.tools['msgfmt'] = state.find_program('msgfmt', for_machine=mesonlib.MachineChoice.BUILD) podir = path.join(state.build_to_src, state.subdir, kwargs['po_dir']) ddirs = self._get_data_dirs(state, kwargs['data_dirs']) @@ -175,11 +178,11 @@ class I18nModule(ExtensionModule): command.extend(state.environment.get_build_command()) command.extend([ '--internal', 'msgfmthelper', - '@INPUT@', '@OUTPUT@', kwargs['type'], podir + '--msgfmt=' + self.tools['msgfmt'].get_path(), ]) if datadirs: command.append(datadirs) - + command.extend(['@INPUT@', '@OUTPUT@', kwargs['type'], podir]) if kwargs['args']: command.append('--') command.extend(kwargs['args']) @@ -219,9 +222,13 @@ class I18nModule(ExtensionModule): ), ) def gettext(self, state: 'ModuleState', args: T.Tuple[str], kwargs: 'Gettext') -> ModuleReturnValue: - if not shutil.which('xgettext'): - self.nogettext_warning() - return ModuleReturnValue(None, []) + for tool in ['msgfmt', 'msginit', 'msgmerge', 'xgettext']: + if self.tools[tool] is None: + self.tools[tool] = state.find_program(tool, required=False, for_machine=mesonlib.MachineChoice.BUILD) + # still not found? + if not self.tools[tool].found(): + self.nogettext_warning(state.current_node) + return ModuleReturnValue(None, []) packagename = args[0] pkg_arg = f'--pkgname={packagename}' @@ -247,6 +254,7 @@ class I18nModule(ExtensionModule): potargs.append(datadirs) if extra_arg: potargs.append(extra_arg) + potargs.append('--xgettext=' + self.tools['xgettext'].get_path()) pottarget = build.RunTarget(packagename + '-pot', potargs, [], state.subdir, state.subproject) targets.append(pottarget) @@ -262,7 +270,7 @@ class I18nModule(ExtensionModule): f'{packagename}-{l}.mo', path.join(state.subdir, l, 'LC_MESSAGES'), state.subproject, - ['msgfmt', '@INPUT@', '-o', '@OUTPUT@'], + [self.tools['msgfmt'], '@INPUT@', '-o', '@OUTPUT@'], [po_file], [f'{packagename}.mo'], install=install, @@ -286,6 +294,8 @@ class I18nModule(ExtensionModule): updatepoargs.append(datadirs) if extra_arg: updatepoargs.append(extra_arg) + for tool in ['msginit', 'msgmerge']: + updatepoargs.append(f'--{tool}=' + self.tools[tool].get_path()) updatepotarget = build.RunTarget(packagename + '-update-po', updatepoargs, [], state.subdir, state.subproject) targets.append(updatepotarget) @@ -306,8 +316,8 @@ class I18nModule(ExtensionModule): KwargInfo('mo_targets', ContainerTypeInfo(list, build.CustomTarget), required=True), ) def itstool_join(self, state: 'ModuleState', args: T.List['TYPE_var'], kwargs: 'ItsJoinFile') -> ModuleReturnValue: - if not shutil.which('itstool'): - self.noitstool_error() + if self.tools['itstool'] is None: + self.tools['itstool'] = state.find_program('itstool', for_machine=mesonlib.MachineChoice.BUILD) mo_targets = kwargs['mo_targets'] its_files = kwargs.get('its_files', []) @@ -321,7 +331,8 @@ class I18nModule(ExtensionModule): command.extend([ '--internal', 'itstool', 'join', '-i', '@INPUT@', - '-o', '@OUTPUT@' + '-o', '@OUTPUT@', + '--itstool=' + self.tools['itstool'].get_path(), ]) if its_files: for fname in its_files: diff --git a/mesonbuild/modules/java.py b/mesonbuild/modules/java.py index dc660a7..6884a22 100644 --- a/mesonbuild/modules/java.py +++ b/mesonbuild/modules/java.py @@ -17,66 +17,70 @@ import pathlib import typing as T from mesonbuild import mesonlib from mesonbuild.build import CustomTarget -from mesonbuild.compilers import detect_compiler_for +from mesonbuild.compilers import detect_compiler_for, Compiler +from mesonbuild.interpreter import Interpreter from mesonbuild.interpreterbase.decorators import ContainerTypeInfo, FeatureDeprecated, FeatureNew, KwargInfo, typed_pos_args, typed_kwargs -from mesonbuild.interpreter.interpreterobjects import FileHolder from mesonbuild.mesonlib import version_compare, MachineChoice -from . import ExtensionModule, ModuleReturnValue, ModuleState -from ..interpreter import Interpreter +from . import NewExtensionModule, ModuleReturnValue, ModuleState -class JavaModule(ExtensionModule): +class JavaModule(NewExtensionModule): @FeatureNew('Java Module', '0.60.0') def __init__(self, interpreter: Interpreter): - super().__init__(interpreter) + super().__init__() self.methods.update({ 'generate_native_header': self.generate_native_header, 'generate_native_headers': self.generate_native_headers, }) - if 'java' not in interpreter.environment.coredata.compilers[MachineChoice.BUILD]: - detect_compiler_for(interpreter.environment, 'java', MachineChoice.BUILD) - self.javac = interpreter.environment.coredata.compilers[MachineChoice.BUILD]['java'] + def __get_java_compiler(self, state: ModuleState) -> Compiler: + if 'java' not in state.environment.coredata.compilers[MachineChoice.BUILD]: + detect_compiler_for(state.environment, 'java', MachineChoice.BUILD) + return state.environment.coredata.compilers[MachineChoice.BUILD]['java'] @FeatureDeprecated('java.generate_native_header', '0.62.0', 'Use java.generate_native_headers instead') - @typed_pos_args('generate_native_header', (str, FileHolder)) + @typed_pos_args('java.generate_native_header', (str, mesonlib.File)) @typed_kwargs('java.generate_native_header', KwargInfo('package', str, default=None)) - def generate_native_header(self, state: ModuleState, args: T.Tuple[T.Union[str, FileHolder]], + def generate_native_header(self, state: ModuleState, args: T.Tuple[T.Union[str, mesonlib.File]], kwargs: T.Dict[str, T.Optional[str]]) -> ModuleReturnValue: package = kwargs.get('package') - file = self.interpreter.source_strings_to_files( - [a.held_object if isinstance(a, FileHolder) else a for a in args])[0] + if isinstance(args[0], mesonlib.File): + file = args[0] + else: + file = mesonlib.File.from_source_file(state.source_root, state.subdir, args[0]) if package: header = f'{package.replace(".", "_")}_{pathlib.Path(file.fname).stem}.h' else: header = f'{pathlib.Path(file.fname).stem}.h' + javac = self.__get_java_compiler(state) + target = CustomTarget( os.path.basename(header), state.subdir, state.subproject, - [ - self.javac.exelist[0], + mesonlib.listify([ + javac.exelist, '-d', '@PRIVATE_DIR@', '-h', state.subdir, '@INPUT@', - ], + ]), [file], [header], backend=state.backend, ) # It is only known that 1.8.0 won't pre-create the directory. 11 and 16 # do not exhibit this behavior. - if version_compare(self.javac.version, '1.8.0'): + if version_compare(javac.version, '1.8.0'): pathlib.Path(state.backend.get_target_private_dir_abs(target)).mkdir(parents=True, exist_ok=True) return ModuleReturnValue(target, [target]) @FeatureNew('java.generate_native_headers', '0.62.0') - @typed_pos_args('generate_native_headers', (str, mesonlib.File), min_varargs=1) + @typed_pos_args('java.generate_native_headers', (str, mesonlib.File), min_varargs=1) @typed_kwargs('java.generate_native_headers', KwargInfo('classes', (ContainerTypeInfo(list, str)), default=[], listify=True, required=True), @@ -94,8 +98,10 @@ class JavaModule(ExtensionModule): else: headers.append(f'{underscore_clazz}.h') + javac = self.__get_java_compiler(state) + command = mesonlib.listify([ - self.javac.exelist, + javac.exelist, '-d', '@PRIVATE_DIR@', '-h', @@ -110,7 +116,7 @@ class JavaModule(ExtensionModule): # It is only known that 1.8.0 won't pre-create the directory. 11 and 16 # do not exhibit this behavior. - if version_compare(self.javac.version, '1.8.0'): + if version_compare(javac.version, '1.8.0'): pathlib.Path(state.backend.get_target_private_dir_abs(target)).mkdir(parents=True, exist_ok=True) return ModuleReturnValue(target, [target]) diff --git a/mesonbuild/modules/unstable_wayland.py b/mesonbuild/modules/unstable_wayland.py index 85da2b7..a7653d9 100644 --- a/mesonbuild/modules/unstable_wayland.py +++ b/mesonbuild/modules/unstable_wayland.py @@ -39,15 +39,18 @@ class WaylandModule(ExtensionModule): @typed_pos_args('wayland.scan_xml', varargs=(str, File), min_varargs=1) @typed_kwargs( 'wayland.scan_xml', - KwargInfo('side', str, default='client', validator=in_set_validator({'client', 'server'})), - KwargInfo('scope', str, default='private', validator=in_set_validator({'private', 'public'})), + KwargInfo('public', bool, default=False), + KwargInfo('client', bool, default=True), + KwargInfo('server', bool, default=False), ) def scan_xml(self, state, args, kwargs): if self.scanner_bin is None: self.scanner_bin = state.find_program('wayland-scanner', for_machine=MachineChoice.BUILD) - scope = kwargs['scope'] - side = kwargs['side'] + scope = 'public' if kwargs['public'] else 'private' + sides = [i for i in ['client', 'server'] if kwargs[i]] + if not sides: + raise MesonException('At least one of client or server keyword argument must be set to true.') xml_files = self.interpreter.source_strings_to_files(args[0]) targets = [] @@ -65,16 +68,17 @@ class WaylandModule(ExtensionModule): ) targets.append(code) - header = CustomTarget( - f'{name}-{side}-protocol', - state.subdir, - state.subproject, - [self.scanner_bin, f'{side}-header', '@INPUT@', '@OUTPUT@'], - [xml_file], - [f'{name}-{side}-protocol.h'], - backend=state.backend, - ) - targets.append(header) + for side in sides: + header = CustomTarget( + f'{name}-{side}-protocol', + state.subdir, + state.subproject, + [self.scanner_bin, f'{side}-header', '@INPUT@', '@OUTPUT@'], + [xml_file], + [f'{name}-{side}-protocol.h'], + backend=state.backend, + ) + targets.append(header) return ModuleReturnValue(targets, targets) diff --git a/mesonbuild/scripts/gettext.py b/mesonbuild/scripts/gettext.py index d6bd366..c329892 100644 --- a/mesonbuild/scripts/gettext.py +++ b/mesonbuild/scripts/gettext.py @@ -24,6 +24,9 @@ parser.add_argument('--datadirs', default='') parser.add_argument('--langs', default='') parser.add_argument('--localedir', default='') parser.add_argument('--subdir', default='') +parser.add_argument('--xgettext', default='xgettext') +parser.add_argument('--msgmerge', default='msgmerge') +parser.add_argument('--msginit', default='msginit') parser.add_argument('--extra-args', default='') def read_linguas(src_sub: str) -> T.List[str]: @@ -42,7 +45,7 @@ def read_linguas(src_sub: str) -> T.List[str]: print(f'Could not find file LINGUAS in {src_sub}') return [] -def run_potgen(src_sub: str, pkgname: str, datadirs: str, args: T.List[str]) -> int: +def run_potgen(src_sub: str, xgettext: str, pkgname: str, datadirs: str, args: T.List[str]) -> int: listfile = os.path.join(src_sub, 'POTFILES.in') if not os.path.exists(listfile): listfile = os.path.join(src_sub, 'POTFILES') @@ -55,18 +58,18 @@ def run_potgen(src_sub: str, pkgname: str, datadirs: str, args: T.List[str]) -> child_env['GETTEXTDATADIRS'] = datadirs ofile = os.path.join(src_sub, pkgname + '.pot') - return subprocess.call(['xgettext', '--package-name=' + pkgname, '-p', src_sub, '-f', listfile, + return subprocess.call([xgettext, '--package-name=' + pkgname, '-p', src_sub, '-f', listfile, '-D', os.environ['MESON_SOURCE_ROOT'], '-k_', '-o', ofile] + args, env=child_env) -def update_po(src_sub: str, pkgname: str, langs: T.List[str]) -> int: +def update_po(src_sub: str, msgmerge: str, msginit: str, pkgname: str, langs: T.List[str]) -> int: potfile = os.path.join(src_sub, pkgname + '.pot') for l in langs: pofile = os.path.join(src_sub, l + '.po') if os.path.exists(pofile): - subprocess.check_call(['msgmerge', '-q', '-o', pofile, pofile, potfile]) + subprocess.check_call([msgmerge, '-q', '-o', pofile, pofile, potfile]) else: - subprocess.check_call(['msginit', '--input', potfile, '--output-file', pofile, '--locale', l, '--no-translator']) + subprocess.check_call([msginit, '--input', potfile, '--output-file', pofile, '--locale', l, '--no-translator']) return 0 def run(args: T.List[str]) -> int: @@ -83,11 +86,11 @@ def run(args: T.List[str]) -> int: langs = read_linguas(src_sub) if subcmd == 'pot': - return run_potgen(src_sub, options.pkgname, options.datadirs, extra_args) + return run_potgen(src_sub, options.xgettext, options.pkgname, options.datadirs, extra_args) elif subcmd == 'update_po': - if run_potgen(src_sub, options.pkgname, options.datadirs, extra_args) != 0: + if run_potgen(src_sub, options.xgettext, options.pkgname, options.datadirs, extra_args) != 0: return 1 - return update_po(src_sub, options.pkgname, langs) + return update_po(src_sub, options.msgmerge, options.msginit, options.pkgname, langs) else: print('Unknown subcommand.') return 1 diff --git a/mesonbuild/scripts/itstool.py b/mesonbuild/scripts/itstool.py index fa3b0fa..72ac3e5 100644 --- a/mesonbuild/scripts/itstool.py +++ b/mesonbuild/scripts/itstool.py @@ -24,11 +24,13 @@ parser.add_argument('command') parser.add_argument('--build-dir', default='') parser.add_argument('-i', '--input', default='') parser.add_argument('-o', '--output', default='') +parser.add_argument('--itstool', default='itstool') parser.add_argument('--its', action='append', default=[]) parser.add_argument('mo_files', nargs='+') -def run_join(build_dir: str, its_files: T.List[str], mo_files: T.List[str], in_fname: str, out_fname: str) -> int: +def run_join(build_dir: str, itstool: str, its_files: T.List[str], mo_files: T.List[str], + in_fname: str, out_fname: str) -> int: if not mo_files: print('No mo files specified to use for translation.') return 1 @@ -53,7 +55,7 @@ def run_join(build_dir: str, its_files: T.List[str], mo_files: T.List[str], in_f shutil.copy(mo_file, tmp_mo_fname) locale_mo_files.append(tmp_mo_fname) - cmd = ['itstool'] + cmd = [itstool] if its_files: for fname in its_files: cmd.extend(['-i', fname]) @@ -73,6 +75,7 @@ def run(args: T.List[str]) -> int: if command == 'join': return run_join(build_dir, + options.itstool, options.its, options.mo_files, options.input, diff --git a/mesonbuild/scripts/msgfmthelper.py b/mesonbuild/scripts/msgfmthelper.py index 3ddc9e6..b308f54 100644 --- a/mesonbuild/scripts/msgfmthelper.py +++ b/mesonbuild/scripts/msgfmthelper.py @@ -22,6 +22,7 @@ parser.add_argument('input') parser.add_argument('output') parser.add_argument('type') parser.add_argument('podir') +parser.add_argument('--msgfmt', default='msgfmt') parser.add_argument('--datadirs', default='') parser.add_argument('args', default=[], metavar='extra msgfmt argument', nargs='*') @@ -32,6 +33,6 @@ def run(args: T.List[str]) -> int: if options.datadirs: env = os.environ.copy() env.update({'GETTEXTDATADIRS': options.datadirs}) - return subprocess.call(['msgfmt', '--' + options.type, '-d', options.podir, + return subprocess.call([options.msgfmt, '--' + options.type, '-d', options.podir, '--template', options.input, '-o', options.output] + options.args, env=env) diff --git a/test cases/cmake/12 generator expressions/subprojects/cmMod/CMakeLists.txt b/test cases/cmake/12 generator expressions/subprojects/cmMod/CMakeLists.txt index dc4f9e4..27b3721 100644 --- a/test cases/cmake/12 generator expressions/subprojects/cmMod/CMakeLists.txt +++ b/test cases/cmake/12 generator expressions/subprojects/cmMod/CMakeLists.txt @@ -12,6 +12,10 @@ target_compile_options(cmModLib INTERFACE "-DCMAKE_FLAG_REQUIRED_A" INTERFACE $<$<AND:1,$<STREQUAL:asd,$<LOWER_CASE:AsD>>,$<NOT:$<EQUAL:4,2>>>:-DCMAKE_FLAG_REQUIRED_B> INTERFACE $<$<VERSION_LESS:1.2.3,2.1.0>:-DCMAKE_FLAG_REQUIRED_C> + INTERFACE $<IF:$<NOT:$<BOOL:OFF>>,-DCMAKE_TRUE_FLAG,-DCMAKE_FALSE_FLAG> + INTERFACE $<IF:$<TARGET_EXISTS:cmModLib>,-DCMAKE_TGT_EXISTS,-DCMAKE_TGT_NEXISTS> + INTERFACE $<IF:$<TARGET_PROPERTY:IMPORTED_NO_SONAME>,-DCMAKE_PROP1_OK,-DCMAKE_PROP1_ERROR> + INTERFACE $<IF:$<TARGET_PROPERTY:cmModLib,IMPORT_SUFFIX>,-DCMAKE_PROP2_ERROR,-DCMAKE_PROP2_OK> ) target_include_directories(cmModLib INTERFACE @@ -19,4 +23,10 @@ target_include_directories(cmModLib INTERFACE $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> ) +set_target_properties(cmModLib + PROPERTIES + IMPORTED_NO_SONAME 1 + IMPORT_SUFFIX 0 +) + target_compile_definitions(cmModLib INTERFACE -DCMAKE_COMPILER_DEFINE_STR="compDef") diff --git a/test cases/cmake/12 generator expressions/subprojects/cmMod/include/cmMod.hpp b/test cases/cmake/12 generator expressions/subprojects/cmMod/include/cmMod.hpp index 1f00107..416e5bb 100644 --- a/test cases/cmake/12 generator expressions/subprojects/cmMod/include/cmMod.hpp +++ b/test cases/cmake/12 generator expressions/subprojects/cmMod/include/cmMod.hpp @@ -18,6 +18,38 @@ #error "The flag CMAKE_FLAG_ERROR_A was set" #endif +#ifndef CMAKE_TRUE_FLAG +#error "The flag CMAKE_TRUE_FLAG was not set" +#endif + +#ifdef CMAKE_FALSE_FLAG +#error "The flag CMAKE_FALSE_FLAG was set" +#endif + +#ifndef CMAKE_TGT_EXISTS +#error "The flag CMAKE_TGT_EXISTS was not set" +#endif + +#ifdef CMAKE_TGT_NEXISTS +#error "The flag CMAKE_TGT_NEXISTS was set" +#endif + +#ifndef CMAKE_PROP1_OK +#error "The flag CMAKE_PROP1_OK was not set" +#endif + +#ifdef CMAKE_PROP1_ERROR +#error "The flag CMAKE_PROP1_ERROR was set" +#endif + +#ifndef CMAKE_PROP2_OK +#error "The flag CMAKE_PROP2_OK was not set" +#endif + +#ifdef CMAKE_PROP2_ERROR +#error "The flag CMAKE_PROP2_ERROR was set" +#endif + class cmModClass { private: std::string str; diff --git a/test cases/cmake/12 generator expressions/test.json b/test cases/cmake/12 generator expressions/test.json new file mode 100644 index 0000000..faf1ff8 --- /dev/null +++ b/test cases/cmake/12 generator expressions/test.json @@ -0,0 +1,5 @@ +{ + "tools": { + "cmake": ">=3.19" + } +} diff --git a/test cases/common/26 find program/meson.build b/test cases/common/26 find program/meson.build index 3b59caa..a083d69 100644 --- a/test cases/common/26 find program/meson.build +++ b/test cases/common/26 find program/meson.build @@ -25,6 +25,9 @@ assert(not prog.found(), 'Version should be too old') prog = find_program('print-version.py', version : '>=1.0') assert(prog.found(), 'Program version should match') +prog = find_program('print-version.py') +assert(prog.version() == '1.0', 'Program version should be detectable') + prog = find_program('print-version-with-prefix.py', version : '>=1.0') assert(prog.found(), 'Program version should match') diff --git a/test cases/frameworks/6 gettext/data/meson.build b/test cases/frameworks/6 gettext/data/meson.build index 1afb403..698eeec 100644 --- a/test cases/frameworks/6 gettext/data/meson.build +++ b/test cases/frameworks/6 gettext/data/meson.build @@ -4,6 +4,7 @@ i18n.merge_file( output: '@BASENAME@', type: 'desktop', po_dir: '../po', + data_dirs: '../po', install: true, install_dir: join_paths(get_option('datadir'), 'applications') ) diff --git a/test cases/frameworks/6 gettext/data2/meson.build b/test cases/frameworks/6 gettext/data2/meson.build index b8c90a1..9a992c9 100644 --- a/test cases/frameworks/6 gettext/data2/meson.build +++ b/test cases/frameworks/6 gettext/data2/meson.build @@ -3,6 +3,7 @@ i18n.merge_file( output: 'test.plugin', type: 'desktop', po_dir: '../po', + data_dirs: '../po', args: ['--keyword=Description'], install: true, install_dir: join_paths(get_option('datadir'), 'applications') diff --git a/test cases/java/9 jni/meson.build b/test cases/java/9 jni/meson.build index 65d7ff6..1239e19 100644 --- a/test cases/java/9 jni/meson.build +++ b/test cases/java/9 jni/meson.build @@ -13,7 +13,7 @@ javamod = import('java') java = find_program('java') -jni_dep = dependency('jni', version : '>=1.8') +jni_dep = dependency('jni', version : '>=1.8', modules: ['jvm', 'awt']) # generate native headers subdir('src/com/mesonbuild') diff --git a/test cases/unit/99 install all targets/meson.build b/test cases/unit/99 install all targets/meson.build index 94bd1fe..3d131e6 100644 --- a/test cases/unit/99 install all targets/meson.build +++ b/test cases/unit/99 install all targets/meson.build @@ -43,6 +43,12 @@ both_libraries('both', 'lib.c', install: true, ) +# Unversioned .so file should have 'devel' tag, others should have 'runtime' tag +shared_library('versioned_shared', 'lib.c', + install: true, + version: '1.2.3', +) + # Those files should have custom tag install_data('bar-custom.txt', install_dir: get_option('datadir'), diff --git a/test cases/wayland/1 client/both.c b/test cases/wayland/1 client/both.c new file mode 100644 index 0000000..b6e1bab --- /dev/null +++ b/test cases/wayland/1 client/both.c @@ -0,0 +1,11 @@ +#include "viewporter-client-protocol.h" +#include "viewporter-server-protocol.h" + +int main() { +#if defined(VIEWPORTER_CLIENT_PROTOCOL_H) && \ + defined(VIEWPORTER_SERVER_PROTOCOL_H) + return 0; +#else + return 1; +#endif +} diff --git a/test cases/wayland/1 client/main.c b/test cases/wayland/1 client/client.c index 6aca80d..4966721 100644 --- a/test cases/wayland/1 client/main.c +++ b/test cases/wayland/1 client/client.c @@ -2,8 +2,8 @@ int main() { #ifdef XDG_SHELL_CLIENT_PROTOCOL_H - return 0; + return 0; #else - return 1; + return 1; #endif } diff --git a/test cases/wayland/3 local/main.c b/test cases/wayland/1 client/local.c index 97bfa56..97bfa56 100644 --- a/test cases/wayland/3 local/main.c +++ b/test cases/wayland/1 client/local.c diff --git a/test cases/wayland/1 client/meson.build b/test cases/wayland/1 client/meson.build index 7ca868b..cb13db2 100644 --- a/test cases/wayland/1 client/meson.build +++ b/test cases/wayland/1 client/meson.build @@ -5,12 +5,44 @@ if not wl_protocols_dep.found() error('MESON_SKIP_TEST: wayland-protocols not installed') endif -wl_dep = dependency('wayland-client') +wl_client_dep = dependency('wayland-client') +wl_server_dep = dependency('wayland-server') wl_mod = import('unstable-wayland') +fs = import('fs') +# Client side only xdg_shell_xml = wl_mod.find_protocol('xdg-shell') xdg_shell = wl_mod.scan_xml(xdg_shell_xml) +assert(xdg_shell.length() == 2) +assert(fs.name(xdg_shell[0].full_path()) == 'xdg-shell-protocol.c') +assert(fs.name(xdg_shell[1].full_path()) == 'xdg-shell-client-protocol.h') +exe = executable('client', 'client.c', xdg_shell, dependencies : wl_client_dep) +test('client', exe) -exe = executable('client', 'main.c', xdg_shell, dependencies : wl_dep) +# Server side only +presentation_time_xml = wl_mod.find_protocol('presentation-time') +presentation_time = wl_mod.scan_xml(presentation_time_xml, client : false, server : true) +assert(presentation_time.length() == 2) +assert(fs.name(presentation_time[0].full_path()) == 'presentation-time-protocol.c') +assert(fs.name(presentation_time[1].full_path()) == 'presentation-time-server-protocol.h') +exe = executable('server', 'server.c', presentation_time, dependencies : wl_server_dep) +test('server', exe) -test('client', exe) +# Both sides +viewporter_xml = wl_mod.find_protocol('viewporter') +viewporter = wl_mod.scan_xml(viewporter_xml, client : true, server : true) +assert(viewporter.length() == 3) +assert(fs.name(viewporter[0].full_path()) == 'viewporter-protocol.c') +assert(fs.name(viewporter[1].full_path()) == 'viewporter-client-protocol.h') +assert(fs.name(viewporter[2].full_path()) == 'viewporter-server-protocol.h') +exe = executable('both', 'both.c', viewporter, dependencies : [wl_client_dep, wl_server_dep]) +test('both', exe) + +# Local xml +xmls = files('test.xml') +gen = wl_mod.scan_xml(xmls) +assert(gen.length() == 2) +assert(fs.name(gen[0].full_path()) == 'test-protocol.c') +assert(fs.name(gen[1].full_path()) == 'test-client-protocol.h') +exe = executable('local', 'local.c', gen, dependencies : wl_client_dep) +test('local', exe) diff --git a/test cases/wayland/1 client/server.c b/test cases/wayland/1 client/server.c new file mode 100644 index 0000000..e073a7b --- /dev/null +++ b/test cases/wayland/1 client/server.c @@ -0,0 +1,9 @@ +#include "presentation-time-server-protocol.h" + +int main() { +#ifdef PRESENTATION_TIME_SERVER_PROTOCOL_H + return 0; +#else + return 1; +#endif +} diff --git a/test cases/wayland/3 local/test.xml b/test cases/wayland/1 client/test.xml index f3c6db1..f3c6db1 100644 --- a/test cases/wayland/3 local/test.xml +++ b/test cases/wayland/1 client/test.xml diff --git a/test cases/wayland/2 server/main.c b/test cases/wayland/2 server/main.c deleted file mode 100644 index 3307499..0000000 --- a/test cases/wayland/2 server/main.c +++ /dev/null @@ -1,9 +0,0 @@ -#include "xdg-shell-server-protocol.h" - -int main() { -#ifdef XDG_SHELL_SERVER_PROTOCOL_H - return 0; -#else - return 1; -#endif -} diff --git a/test cases/wayland/2 server/meson.build b/test cases/wayland/2 server/meson.build deleted file mode 100644 index c93ff11..0000000 --- a/test cases/wayland/2 server/meson.build +++ /dev/null @@ -1,16 +0,0 @@ -project('wayland-test-server', 'c') - -wl_protocols_dep = dependency('wayland-protocols', required : false) -if not wl_protocols_dep.found() - error('MESON_SKIP_TEST: wayland-protocols not installed') -endif - -wl_dep = dependency('wayland-server') -wl_mod = import('unstable-wayland') - -xdg_shell_xml = wl_mod.find_protocol('xdg-shell') -xdg_shell = wl_mod.scan_xml(xdg_shell_xml, side : 'server') - -exe = executable('server', 'main.c', xdg_shell, dependencies : wl_dep) - -test('client', exe) diff --git a/test cases/wayland/3 local/meson.build b/test cases/wayland/3 local/meson.build deleted file mode 100644 index 7a470d6..0000000 --- a/test cases/wayland/3 local/meson.build +++ /dev/null @@ -1,11 +0,0 @@ -project('wayland-test-local', 'c') - -wl_dep = dependency('wayland-client') -wl_mod = import('unstable-wayland') - -xmls = files('test.xml') -gen = wl_mod.scan_xml(xmls) - -exe = executable('local', 'main.c', gen, dependencies : wl_dep) - -test('local', exe) diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index 3eea9a3..665e0c6 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -3886,10 +3886,12 @@ class AllPlatformTests(BasePlatformTests): Path(installpath, 'usr/bin/both2.pdb'), Path(installpath, 'usr/bin/bothcustom.pdb'), Path(installpath, 'usr/bin/shared.pdb'), + Path(installpath, 'usr/bin/versioned_shared-1.pdb'), Path(installpath, 'usr/lib/both.lib'), Path(installpath, 'usr/lib/both2.lib'), Path(installpath, 'usr/lib/bothcustom.lib'), Path(installpath, 'usr/lib/shared.lib'), + Path(installpath, 'usr/lib/versioned_shared.lib'), } elif is_windows() or is_cygwin(): expected_devel |= { @@ -3897,6 +3899,11 @@ class AllPlatformTests(BasePlatformTests): Path(installpath, 'usr/lib/libboth2.dll.a'), Path(installpath, 'usr/lib/libshared.dll.a'), Path(installpath, 'usr/lib/libbothcustom.dll.a'), + Path(installpath, 'usr/lib/libversioned_shared.dll.a'), + } + else: + expected_devel |= { + Path(installpath, 'usr/' + shared_lib_name('versioned_shared')), } expected_runtime = expected_common | { @@ -3908,6 +3915,20 @@ class AllPlatformTests(BasePlatformTests): Path(installpath, 'usr/' + shared_lib_name('both2')), } + if is_windows() or is_cygwin(): + expected_runtime |= { + Path(installpath, 'usr/' + shared_lib_name('versioned_shared-1')), + } + elif is_osx(): + expected_runtime |= { + Path(installpath, 'usr/' + shared_lib_name('versioned_shared.1')), + } + else: + expected_runtime |= { + Path(installpath, 'usr/' + shared_lib_name('versioned_shared') + '.1'), + Path(installpath, 'usr/' + shared_lib_name('versioned_shared') + '.1.2.3'), + } + expected_custom = expected_common | { Path(installpath, 'usr/share'), Path(installpath, 'usr/share/bar-custom.txt'), |