diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 18caf7bbe8a7..60704508025b 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -25,6 +25,7 @@ from .. import mesonlib from .. import mlog from ..compilers import LANGUAGES_USING_LDFLAGS, detect, lang_suffixes +from ..compilers.mixins.clang import ClangCompiler from ..mesonlib import ( File, MachineChoice, MesonException, OrderedSet, ExecutableSerialisation, EnvironmentException, @@ -343,22 +344,30 @@ def get_build_dir_include_args(self, target: build.BuildTarget, compiler: 'Compi return compiler.get_include_args(curdir, False) def get_target_filename_for_linking(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> T.Optional[str]: + if isinstance(target, (build.BuildTarget, build.CustomTarget, build.CustomTargetIndex)): + target_filename = target.get_filename() + + if isinstance(target, build.BundleTarget): + # TODO: what's the difference linking a framework like this vs. the bespoke framework arg on macOS's ld? + # (this also has the advantage of working on other platforms!) + target_filename = os.path.join(target_filename, target.get_bundle_info().get_executable_path()) + # On some platforms (msvc for instance), the file that is used for # dynamic linking is not the same as the dynamic library itself. This # file is called an import library, and we want to link against that. # On all other platforms, we link to the library directly. if isinstance(target, build.SharedLibrary): - link_lib = target.get_import_filename() or target.get_filename() + link_lib = target.get_import_filename() or target_filename # In AIX, if we archive .so, the blibpath must link to archived shared library otherwise to the .so file. if mesonlib.is_aix() and target.aix_so_archive: link_lib = re.sub('[.][a]([.]?([0-9]+))*([.]?([a-z]+))*', '.a', link_lib.replace('.so', '.a')) return Path(self.get_target_dir(target), link_lib).as_posix() elif isinstance(target, build.StaticLibrary): - return Path(self.get_target_dir(target), target.get_filename()).as_posix() + return Path(self.get_target_dir(target), target_filename).as_posix() elif isinstance(target, (build.CustomTarget, build.CustomTargetIndex)): if not target.is_linkable_target(): raise MesonException(f'Tried to link against custom target "{target.name}", which is not linkable.') - return Path(self.get_target_dir(target), target.get_filename()).as_posix() + return Path(self.get_target_dir(target), target_filename).as_posix() elif isinstance(target, build.Executable): if target.import_filename: return Path(self.get_target_dir(target), target.get_import_filename()).as_posix() @@ -817,6 +826,7 @@ def determine_rpath_dirs(self, target: T.Union[build.BuildTarget, build.CustomTa # Need a copy here result = OrderedSet(target.get_link_dep_subdirs()) else: + # TODO: Bundle handling result = OrderedSet() result.add('meson-out') if isinstance(target, build.BuildTarget): @@ -1077,11 +1087,18 @@ def generate_basic_compiler_args(self, target: build.BuildTarget, compiler: 'Com commands += dep.get_exe_args(compiler) # For 'automagic' deps: Boost and GTest. Also dependency('threads'). # pkg-config puts the thread flags itself via `Cflags:` - # Fortran requires extra include directives. - if compiler.language == 'fortran': - for lt in chain(target.link_targets, target.link_whole_targets): + + for lt in chain(target.link_targets, target.link_whole_targets): + # Fortran requires extra include directives. + if compiler.language == 'fortran': priv_dir = self.get_target_private_dir(lt) commands += compiler.get_include_args(priv_dir, False) + + # Add linked frameworks to include path + if isinstance(lt, build.FrameworkBundle): + # TODO: Handle this in the compiler class? + assert isinstance(compiler, ClangCompiler), 'Linking against frameworks requires clang' + commands += [f'-F{self.get_target_dir(lt)}', '-framework', lt.get_basename()] return commands def build_target_link_arguments(self, compiler: 'Compiler', deps: T.List[build.Target]) -> T.List[str]: diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 47b0ec0820d4..041a4d60ab2f 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -3248,6 +3248,15 @@ def generate_single_compile(self, target: build.BuildTarget, src, element.add_dep(pch_dep) for i in self.get_fortran_orderdeps(target, compiler): element.add_orderdep(i) + + for lt in itertools.chain(target.link_targets, target.link_whole_targets): + # Linking against frameworks also pulls in its headers, therefore wait for it to be assembled. This is not + # exactly ideal, since it will also wait for the library to be linked, but it's better than no dep at all. + # TODO: check whether linking against framework is possible when only headers have been installed into the + # bundle + if isinstance(lt, build.FrameworkBundle): + element.add_orderdep(self.get_target_filename(lt)) + if dep_file: element.add_item('DEPFILE', dep_file) if compiler.get_language() == 'cuda': @@ -3684,6 +3693,11 @@ def generate_link(self, target: build.BuildTarget, outname, obj_list, linker: T. self.get_target_dir(target)) else: target_slashname_workaround_dir = self.get_target_dir(target) + + if isinstance(target, build.BundleTarget): + target_slashname_workaround_dir = str(PurePath() / target_slashname_workaround_dir / target.get_filename() / + target.get_bundle_info().get_executable_folder_path()) + (rpath_args, target.rpath_dirs_to_remove) = ( linker.build_rpath_args(self.environment, self.environment.get_build_dir(), diff --git a/mesonbuild/build.py b/mesonbuild/build.py index c44e019022d3..5f3f5261e538 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -1127,7 +1127,10 @@ def get_transitive_link_deps_mapping(self, prefix: str) -> T.Mapping[str, str]: def get_link_dep_subdirs(self) -> T.AbstractSet[str]: result: OrderedSet[str] = OrderedSet() for i in self.link_targets: - if not isinstance(i, StaticLibrary): + if isinstance(i, FrameworkBundle): + result.add(str(pathlib.PurePath() / i.get_subdir() / i.get_filename() / + i.get_bundle_info().get_executable_folder_path())) + elif not isinstance(i, StaticLibrary): result.add(i.get_subdir()) result.update(i.get_link_dep_subdirs()) return result