Skip to content

Commit

Permalink
Add bundle target types
Browse files Browse the repository at this point in the history
  • Loading branch information
2xsaiko committed Jan 23, 2025
1 parent 74e7ac4 commit 3234e7f
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 5 deletions.
2 changes: 1 addition & 1 deletion docs/markdown/Commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
125 changes: 125 additions & 0 deletions mesonbuild/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
PerMachineDefaultable,
MesonBugException, EnvironmentVariables, pickle_load,
)
from .nsbundle import BundleInfo, BundleType, TargetBundle
from .options import OptionKey

from .compilers import (
Expand Down Expand Up @@ -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 |
Expand All @@ -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]]:
Expand Down Expand Up @@ -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):

Expand Down
8 changes: 8 additions & 0 deletions mesonbuild/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'))

Expand Down
11 changes: 7 additions & 4 deletions mesonbuild/interpreter/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions mesonbuild/interpreter/interpreterobjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
19 changes: 19 additions & 0 deletions mesonbuild/interpreter/kwargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
32 changes: 32 additions & 0 deletions mesonbuild/interpreter/type_checking.py
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand All @@ -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],
Expand Down
3 changes: 3 additions & 0 deletions mesonbuild/mcompile.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ def _is_valid_type(type: str) -> bool:
'alias',
'run',
'jar',
'nsapp',
'nsframework',
'nsbundle',
}
return type in allowed_types

Expand Down

0 comments on commit 3234e7f

Please sign in to comment.