From 3234e7f7c647b4903ba629e3eff995ec46fe666f Mon Sep 17 00:00:00 2001 From: Marco Rebhan Date: Thu, 2 Jan 2025 21:35:49 +0100 Subject: [PATCH] Add bundle target types --- docs/markdown/Commands.md | 2 +- mesonbuild/build.py | 125 +++++++++++++++++++ mesonbuild/environment.py | 8 ++ mesonbuild/interpreter/interpreter.py | 11 +- mesonbuild/interpreter/interpreterobjects.py | 6 + mesonbuild/interpreter/kwargs.py | 19 +++ mesonbuild/interpreter/type_checking.py | 32 +++++ mesonbuild/mcompile.py | 3 + 8 files changed, 201 insertions(+), 5 deletions(-) diff --git a/docs/markdown/Commands.md b/docs/markdown/Commands.md index a99deb4941a9..2813d9d9dfbe 100644 --- a/docs/markdown/Commands.md +++ b/docs/markdown/Commands.md @@ -62,7 +62,7 @@ Builds a default or a specified target of a configured Meson project. - `NAME`: name of the target from `meson.build` (e.g. `foo` from `executable('foo', ...)`). - `SUFFIX`: name of the suffix of the target from `meson.build` (e.g. `exe` from `executable('foo', suffix: 'exe', ...)`). - `PATH`: path to the target relative to the root `meson.build` file. Note: relative path for a target specified in the root `meson.build` is `./`. -- `TYPE`: type of the target. Can be one of the following: 'executable', 'static_library', 'shared_library', 'shared_module', 'custom', 'alias', 'run', 'jar'. +- `TYPE`: type of the target. Can be one of the following: 'executable', 'static_library', 'shared_library', 'shared_module', 'custom', 'alias', 'run', 'jar', 'nsapp', 'nsframework', 'nsbundle'. `PATH`, `SUFFIX`, and `TYPE` can all be omitted if the resulting `TARGET` can be used to uniquely identify the target in `meson.build`. diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 0c431f5205aa..ec747feafdf1 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -26,6 +26,7 @@ PerMachineDefaultable, MesonBugException, EnvironmentVariables, pickle_load, ) +from .nsbundle import BundleInfo, BundleType, TargetBundle from .options import OptionKey from .compilers import ( @@ -103,6 +104,13 @@ class DFeatures(TypedDict): 'win_subsystem', } +bundle_kwargs = { + 'bundle_resources', + 'bundle_contents', + 'bundle_extra_binaries', + 'info_plist', +} + known_build_target_kwargs = ( buildtarget_kwargs | lang_arg_kwargs | @@ -116,6 +124,8 @@ class DFeatures(TypedDict): known_shmod_kwargs = known_build_target_kwargs | {'vs_module_defs', 'rust_abi'} known_stlib_kwargs = known_build_target_kwargs | {'pic', 'prelink', 'rust_abi'} known_jar_kwargs = known_exe_kwargs | {'main_class', 'java_resources'} +known_nsapp_kwargs = known_exe_kwargs | bundle_kwargs | {'bundle_layout', 'bundle_exe_dir_name'} +known_nsframework_kwargs = known_exe_kwargs | bundle_kwargs def _process_install_tag(install_tag: T.Optional[T.List[T.Optional[str]]], num_outputs: int) -> T.List[T.Optional[str]]: @@ -3009,6 +3019,121 @@ def get_classpath_args(self): def get_default_install_dir(self) -> T.Union[T.Tuple[str, str], T.Tuple[None, None]]: return self.environment.get_jar_dir(), '{jardir}' + +def _fill_bundle_info_from_kwargs(bi: BundleInfo, kwargs) -> None: + bi.resources = kwargs['bundle_resources'] + bi.contents = kwargs['bundle_contents'] + bi.extra_binaries = kwargs.get('bundle_extra_binaries', []) + + if kwargs['info_plist'] is not None: + bi.info_dict_file = _source_input_to_file(bi.owner, 'info_plist', kwargs['info_plist']) + + +def _source_input_to_file(t: Target, kw: str, source: T.Union[str, File, CustomTarget, CustomTargetIndex]) -> File: + if isinstance(source, str): + if os.path.isabs(source): + return File.from_absolute_file(source) + else: + return File.from_source_file(t.environment.source_dir, t.subdir, source) + elif isinstance(source, File): + # When passing a generated file. + return source + elif isinstance(source, (CustomTarget, CustomTargetIndex)): + # When passing output of a Custom Target + return File.from_built_file(source.get_subdir(), source.get_filename()) + else: + raise InvalidArguments( + f'{kw} must be either a string, ' + 'a file object, a Custom Target, or a Custom Target Index') + + +class AppBundle(Executable, TargetBundle): + known_kwargs = known_nsapp_kwargs + + typename = 'nsapp' + + def __init__(self, name: str, subdir: str, subproject: SubProject, for_machine: MachineChoice, + sources: T.List[SourceOutputs], structured_sources: T.Optional[StructuredSources], + objects: T.List[ObjectTypes], environment: environment.Environment, compilers: T.Dict[str, Compiler], + kwargs): + super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, environment, + compilers, kwargs) + + self.bundle_info = BundleInfo(self) + _fill_bundle_info_from_kwargs(self.bundle_info, kwargs) + + if kwargs['bundle_layout'] is not None: + self.bundle_info.layout = kwargs['bundle_layout'] + if kwargs['bundle_exe_dir_name'] is not None: + self.bundle_info.executable_folder_name: T.Optional[str] = kwargs['bundle_exe_dir_name'] + + def type_suffix(self) -> str: + return '@nsapp' + + def post_init(self) -> None: + super().post_init() + self.outputs[0] = self.get_filename() + + def get_filename(self) -> str: + return self.bundle_info.get_wrapper_name() + + def get_bundle_info(self) -> BundleInfo: + return self.bundle_info + + def get_bundle_type(self) -> BundleType: + return BundleType.APPLICATION + + def get_executable_name(self) -> str: + return self.filename + + def can_output_be_directory(self, output: str) -> bool: + return output == self.get_filename() + + def get_default_install_dir(self) -> T.Union[T.Tuple[str, str], T.Tuple[None, None]]: + return self.environment.get_app_dir(), '{appdir}' + + +class FrameworkBundle(SharedLibrary, TargetBundle): + known_kwargs = known_nsframework_kwargs + + typename = 'nsframework' + + def __init__(self, name: str, subdir: str, subproject: SubProject, for_machine: MachineChoice, + sources: T.List[SourceOutputs], structured_sources: T.Optional[StructuredSources], + objects: T.List[ObjectTypes], environment: environment.Environment, compilers: T.Dict[str, Compiler], + kwargs): + super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, environment, + compilers, kwargs) + + self.bundle_info = BundleInfo(self) + _fill_bundle_info_from_kwargs(self.bundle_info, kwargs) + + def type_suffix(self) -> str: + return '@nsframework' + + def post_init(self) -> None: + super().post_init() + self.outputs[0] = self.get_filename() + + def get_filename(self) -> str: + return self.bundle_info.get_wrapper_name() + + def get_bundle_info(self) -> BundleInfo: + return self.bundle_info + + def get_bundle_type(self) -> BundleType: + return BundleType.FRAMEWORK + + def get_executable_name(self) -> str: + return self.filename + + def can_output_be_directory(self, output: str) -> bool: + return output == self.get_filename() + + def get_default_install_dir(self) -> T.Union[T.Tuple[str, str], T.Tuple[None, None]]: + return self.environment.get_framework_dir(), '{frameworkdir}' + + @dataclass(eq=False) class CustomTargetIndex(CustomTargetBase, HoldableObject): diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index c09d7e312ab7..d6c843951874 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -935,6 +935,14 @@ def get_static_lib_dir(self) -> str: "Install dir for the static library" return self.get_libdir() + def get_app_dir(self) -> str: + """Install dir for application bundles""" + return f"{self.get_prefix()}/Applications" + + def get_framework_dir(self) -> str: + """Install dir for framework bundles""" + return f"{self.get_prefix()}/Library/Frameworks" + def get_prefix(self) -> str: return self.coredata.get_option(OptionKey('prefix')) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 7ba5ebf69ef4..70513c01ecd2 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -429,6 +429,8 @@ def build_holder_map(self) -> None: build.SharedModule: OBJ.SharedModuleHolder, build.Executable: OBJ.ExecutableHolder, build.Jar: OBJ.JarHolder, + build.AppBundle: OBJ.AppBundleHolder, + build.FrameworkBundle: OBJ.FrameworkBundleHolder, build.CustomTarget: OBJ.CustomTargetHolder, build.CustomTargetIndex: OBJ.CustomTargetIndexHolder, build.Generator: OBJ.GeneratorHolder, @@ -3415,16 +3417,17 @@ def create_build_target(self, node: mparser.BaseNode, args: T.Tuple[str, Sources kwargs['dependencies'] = extract_as_list(kwargs, 'dependencies') kwargs['extra_files'] = self.source_strings_to_files(kwargs['extra_files']) self.check_sources_exist(os.path.join(self.source_root, self.subdir), sources) - if targetclass not in {build.Executable, build.SharedLibrary, build.SharedModule, build.StaticLibrary, build.Jar}: + if targetclass not in {build.Executable, build.SharedLibrary, build.SharedModule, build.StaticLibrary, + build.Jar, build.AppBundle, build.FrameworkBundle}: mlog.debug('Unknown target type:', str(targetclass)) raise RuntimeError('Unreachable code') self.__process_language_args(kwargs) - if targetclass is build.StaticLibrary: + if issubclass(targetclass, build.StaticLibrary): for lang in compilers.all_languages - {'java'}: deps, args = self.__convert_file_args(kwargs.get(f'{lang}_static_args', [])) kwargs['language_args'][lang].extend(args) kwargs['depend_files'].extend(deps) - elif targetclass is build.SharedLibrary: + elif issubclass(targetclass, build.SharedLibrary): for lang in compilers.all_languages - {'java'}: deps, args = self.__convert_file_args(kwargs.get(f'{lang}_shared_args', [])) kwargs['language_args'][lang].extend(args) @@ -3469,7 +3472,7 @@ def create_build_target(self, node: mparser.BaseNode, args: T.Tuple[str, Sources kwargs['include_directories'] = self.extract_incdirs(kwargs) - if targetclass is build.Executable: + if issubclass(targetclass, build.Executable): kwargs = T.cast('kwtypes.Executable', kwargs) if kwargs['gui_app'] is not None: if kwargs['win_subsystem'] is not None: diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index f4a2b4107ed3..a81a6adb6d62 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -1032,6 +1032,12 @@ class SharedModuleHolder(BuildTargetHolder[build.SharedModule]): class JarHolder(BuildTargetHolder[build.Jar]): pass +class AppBundleHolder(BuildTargetHolder[build.AppBundle]): + pass + +class FrameworkBundleHolder(BuildTargetHolder[build.FrameworkBundle]): + pass + class CustomTargetIndexHolder(ObjectHolder[build.CustomTargetIndex]): def __init__(self, target: build.CustomTargetIndex, interp: 'Interpreter'): super().__init__(target, interp) diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index 87f121e90b0f..c70f16bc6b18 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -453,6 +453,25 @@ class Library(_BuildTarget, _SharedLibMixin, _StaticLibMixin, _LibraryMixin): masm_shared_args: NotRequired[T.List[str]] +class BundleShared(TypedDict): + + bundle_resources: T.Optional[build.StructuredSources] + bundle_contents: T.Optional[build.StructuredSources] + bundle_extra_binaries: T.Optional[build.StructuredSources] + info_plist: T.Optional[T.Union[str, File, build.CustomTarget, build.CustomTargetIndex]] + + +class AppBundle(Executable, BundleShared): + + bundle_exe_dir_name: T.Optional[str] + bundle_layout: T.Optional[str] + + +class FrameworkBundle(SharedLibrary, BundleShared): + + pass + + class BuildTarget(Library): target_type: Literal['executable', 'shared_library', 'static_library', diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index ed34be950065..076573b968c4 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -792,6 +792,36 @@ def _convert_darwin_versions(val: T.List[T.Union[str, int]]) -> T.Optional[T.Tup for a in _LANGUAGE_KWS], ] +_BUNDLE_KWS: T.List[KwargInfo] = [ + KwargInfo('bundle_resources', (StructuredSources, NoneType)), + KwargInfo('bundle_contents', (StructuredSources, NoneType)), + KwargInfo('bundle_extra_binaries', (StructuredSources, NoneType)), + KwargInfo('info_plist', (str, File, CustomTarget, CustomTargetIndex, NoneType)), +] + +_EXCLUSIVE_NSAPP_KWS: T.List[KwargInfo] = [ + KwargInfo('bundle_layout', (str, NoneType)), + KwargInfo('bundle_exe_dir_name', (StructuredSources, NoneType)), +] + +_EXCLUSIVE_NSFRAMEWORK_KWS: T.List[KwargInfo] = [] + +NSAPP_KWS: T.List[KwargInfo] = [ + *EXECUTABLE_KWS, + *_BUNDLE_KWS, + *_EXCLUSIVE_NSAPP_KWS, + INCLUDE_DIRECTORIES, + DEPENDENCIES_KW, +] + +NSFRAMEWORK_KWS: T.List[KwargInfo] = [ + *SHARED_LIB_KWS, + *_BUNDLE_KWS, + *_EXCLUSIVE_NSFRAMEWORK_KWS, + INCLUDE_DIRECTORIES, + DEPENDENCIES_KW, +] + _SHARED_STATIC_ARGS: T.List[KwargInfo[T.List[str]]] = [ *[l.evolve(name=l.name.replace('_', '_static_'), since='1.3.0') for l in _LANGUAGE_KWS], @@ -818,6 +848,8 @@ def _convert_darwin_versions(val: T.List[T.Union[str, int]]) -> T.Optional[T.Tup *_EXCLUSIVE_SHARED_MOD_KWS, *_EXCLUSIVE_STATIC_LIB_KWS, *_EXCLUSIVE_EXECUTABLE_KWS, + *_EXCLUSIVE_NSAPP_KWS, + *_EXCLUSIVE_NSFRAMEWORK_KWS, *_SHARED_STATIC_ARGS, *[a.evolve(deprecated='1.3.0', deprecated_message='The use of "jar" in "build_target()" is deprecated, and this argument is only used by jar()') for a in _EXCLUSIVE_JAR_KWS], diff --git a/mesonbuild/mcompile.py b/mesonbuild/mcompile.py index 2f5708c86521..087f720775b8 100644 --- a/mesonbuild/mcompile.py +++ b/mesonbuild/mcompile.py @@ -91,6 +91,9 @@ def _is_valid_type(type: str) -> bool: 'alias', 'run', 'jar', + 'nsapp', + 'nsframework', + 'nsbundle', } return type in allowed_types