diff --git a/docs/markdown/snippets/rust-objects.md b/docs/markdown/snippets/rust-objects.md new file mode 100644 index 000000000000..575e1f6e38ed --- /dev/null +++ b/docs/markdown/snippets/rust-objects.md @@ -0,0 +1,4 @@ +## `objects` added correctly to Rust executables + +Any objects included in a Rust executable were previously ignored. They +are now added correctly. diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index a6a42e9334bf..972a646e3c44 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -467,6 +467,14 @@ def relpath(todir: str, fromdir: str) -> str: return os.path.relpath(os.path.join('dummyprefixdir', todir), os.path.join('dummyprefixdir', fromdir)) + def get_compiler_order_deps(self, target: build.BuildTarget, compiler: Compiler) -> T.List[str]: + return [os.path.join(self.get_target_dir(lt), lt.get_filename()) + for lt in compiler.get_extra_order_deps(target)] + + def get_fortran_order_deps(self, deps: T.List[build.BuildTarget]) -> T.List[File]: + return [File(True, *os.path.split(self.get_target_filename(t))) for t in deps + if t.uses_fortran()] + def flatten_object_list(self, target: build.BuildTarget, proj_dir_to_build_root: str = '' ) -> T.Tuple[T.List[str], T.List[build.BuildTargetTypes]]: obj_list, deps = self._flatten_object_list(target, target.get_objects(), proj_dir_to_build_root) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 57d73ed1810c..f9c7a553b9c6 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1025,14 +1025,13 @@ def generate_target(self, target) -> None: pch_objects = [] o, od = self.flatten_object_list(target) - obj_targets = [t for t in od if t.uses_fortran()] obj_list.extend(o) - fortran_order_deps = [File(True, *os.path.split(self.get_target_filename(t))) for t in obj_targets] + fortran_order_deps = self.get_fortran_order_deps(od) fortran_inc_args: T.List[str] = [] if target.uses_fortran(): fortran_inc_args = mesonlib.listify([target.compilers['fortran'].get_include_args( - self.get_target_private_dir(t), is_system=False) for t in obj_targets]) + self.get_target_private_dir(t), is_system=False) for t in od if t.uses_fortran()]) # Generate compilation targets for sources generated by transpilers. # @@ -1930,24 +1929,12 @@ def _get_rust_dependency_name(self, target: build.BuildTarget, dependency: LibTy # in Rust return target.rust_dependency_map.get(dependency.name, dependency.name).replace('-', '_') - def generate_rust_target(self, target: build.BuildTarget) -> None: - rustc = target.compilers['rust'] + def generate_rust_sources(self, target: build.BuildTarget) -> T.Tuple[T.List[str], str]: + orderdeps: T.List[str] = [] + # Rust compiler takes only the main file as input and # figures out what other files are needed via import # statements and magic. - args = rustc.compiler_args() - # Compiler args for compiling this target - args += compilers.get_base_compile_args(target, rustc, self.environment) - self.generate_generator_list_rules(target) - - # dependencies need to cause a relink, they're not just for ordering - deps: T.List[str] = [] - - # Dependencies for rust-project.json - project_deps: T.List[RustDep] = [] - - orderdeps: T.List[str] = [] - main_rust_file = None if target.structured_sources: if target.structured_sources.needs_copy(): @@ -1977,14 +1964,10 @@ def generate_rust_target(self, target: build.BuildTarget) -> None: orderdeps.extend(_ods) for i in target.get_sources(): - if not rustc.can_compile(i): - raise InvalidArguments(f'Rust target {target.get_basename()} contains a non-rust source file.') if main_rust_file is None: main_rust_file = i.rel_to_builddir(self.build_to_src) for g in target.get_generated_sources(): for i in g.get_outputs(): - if not rustc.can_compile(i): - raise InvalidArguments(f'Rust target {target.get_basename()} contains a non-rust source file.') if isinstance(g, GeneratedList): fname = os.path.join(self.get_target_private_dir(target), i) else: @@ -1992,26 +1975,35 @@ def generate_rust_target(self, target: build.BuildTarget) -> None: if main_rust_file is None: main_rust_file = fname orderdeps.append(fname) - if main_rust_file is None: - raise RuntimeError('A Rust target has no Rust sources. This is weird. Also a bug. Please report') + + return orderdeps, main_rust_file + + def get_rust_compiler_args(self, target: build.BuildTarget, rustc: Compiler, + depfile: T.Optional[str] = None) -> T.List[str]: + # Compiler args for compiling this target + args = compilers.get_base_compile_args(target, rustc, self.environment) + target_name = self.get_target_filename(target) args.extend(['--crate-type', target.rust_crate_type]) # If we're dynamically linking, add those arguments - # - # Rust is super annoying, calling -C link-arg foo does not work, it has - # to be -C link-arg=foo if target.rust_crate_type in {'bin', 'dylib'}: args.extend(rustc.get_linker_always_args()) args += self.generate_basic_compiler_args(target, rustc) # Rustc replaces - with _. spaces or dots are not allowed, so we replace them with underscores args += ['--crate-name', target.name.replace('-', '_').replace(' ', '_').replace('.', '_')] - depfile = os.path.join(self.get_target_private_dir(target), target.name + '.d') - args += rustc.get_dependency_gen_args(target_name, depfile) + if depfile: + args += rustc.get_dependency_gen_args(target_name, depfile) args += rustc.get_output_args(target_name) args += ['-C', 'metadata=' + target.get_id()] args += target.get_extra_args('rust') + return args + + def get_rust_compiler_deps_and_args(self, target: build.BuildTarget, rustc: Compiler) -> T.Tuple[T.List[str], T.List[str], T.List[RustDep], T.List[str]]: + deps: T.List[str] = [] + project_deps: T.List[RustDep] = [] + args: T.List[str] = [] # Rustc always use non-debug Windows runtime. Inject the one selected # by Meson options instead. @@ -2040,6 +2032,12 @@ def _link_library(libname: str, static: bool, bundle: bool = False): type_ += ':' + ','.join(modifiers) args.append(f'-l{type_}={libname}') + objs, od = self.flatten_object_list(target) + for o in objs: + args.append(f'-Clink-arg={o}') + deps.append(o) + fortran_order_deps = self.get_fortran_order_deps(od) + linkdirs = mesonlib.OrderedSet() external_deps = target.external_deps.copy() target_deps = target.get_dependencies() @@ -2125,6 +2123,33 @@ def _link_library(libname: str, static: bool, bundle: bool = False): if isinstance(target, build.SharedLibrary) or has_shared_deps: args += self.get_build_rpath_args(target, rustc) + return deps, fortran_order_deps, project_deps, args + + def generate_rust_target(self, target: build.BuildTarget) -> None: + rustc = target.compilers['rust'] + self.generate_generator_list_rules(target) + + for i in target.get_sources(): + if not rustc.can_compile(i): + raise InvalidArguments(f'Rust target {target.get_basename()} contains a non-rust source file.') + for g in target.get_generated_sources(): + for i in g.get_outputs(): + if not rustc.can_compile(i): + raise InvalidArguments(f'Rust target {target.get_basename()} contains a non-rust source file.') + + orderdeps, main_rust_file = self.generate_rust_sources(target) + target_name = self.get_target_filename(target) + if main_rust_file is None: + raise RuntimeError('A Rust target has no Rust sources. This is weird. Also a bug. Please report') + + args = rustc.compiler_args() + + depfile = os.path.join(self.get_target_private_dir(target), target.name + '.d') + args += self.get_rust_compiler_args(target, rustc, depfile) + + deps, fortran_order_deps, project_deps, deps_args = self.get_rust_compiler_deps_and_args(target, rustc) + args += deps_args + proc_macro_dylib_path = None if target.rust_crate_type == 'proc-macro': proc_macro_dylib_path = self.get_target_filename_abs(target) @@ -2140,7 +2165,10 @@ def _link_library(libname: str, static: bool, bundle: bool = False): element = NinjaBuildElement(self.all_outputs, target_name, compiler_name, main_rust_file) if orderdeps: element.add_orderdep(orderdeps) + if fortran_order_deps: + element.add_orderdep(fortran_order_deps) if deps: + # dependencies need to cause a relink, they're not just for ordering element.add_dep(deps) element.add_item('ARGS', args) element.add_item('targetdep', depfile) @@ -3109,7 +3137,7 @@ def generate_single_compile(self, target: build.BuildTarget, src, d = os.path.join(self.get_target_private_dir(target), d) element.add_orderdep(d) element.add_dep(pch_dep) - for i in self.get_fortran_orderdeps(target, compiler): + for i in self.get_compiler_order_deps(target, compiler): element.add_orderdep(i) if dep_file: element.add_item('DEPFILE', dep_file) @@ -3174,20 +3202,6 @@ def has_dir_part(self, fname: FileOrString) -> bool: return fname.subdir != '' return has_path_sep(fname) - # Fortran is a bit weird (again). When you link against a library, just compiling a source file - # requires the mod files that are output when single files are built. To do this right we would need to - # scan all inputs and write out explicit deps for each file. That is too slow and too much effort so - # instead just have an ordered dependency on the library. This ensures all required mod files are created. - # The real deps are then detected via dep file generation from the compiler. This breaks on compilers that - # produce incorrect dep files but such is life. - def get_fortran_orderdeps(self, target, compiler): - if compiler.language != 'fortran': - return [] - return [ - os.path.join(self.get_target_dir(lt), lt.get_filename()) - for lt in itertools.chain(target.link_targets, target.link_whole_targets) - ] - def generate_msvc_pch_command(self, target, compiler, pch): header = pch[0] pchname = compiler.get_pch_name(header) diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index e4c7f77441a2..7312e05e0576 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -25,7 +25,7 @@ if T.TYPE_CHECKING: from .. import coredata - from ..build import BuildTarget, DFeatures + from ..build import BuildTarget, BuildTargetTypes, DFeatures from ..coredata import MutableKeyedOptionDictType, KeyedOptionDictType from ..envconfig import MachineInfo from ..environment import Environment @@ -1279,6 +1279,9 @@ def get_module_outdir_args(self, path: str) -> T.List[str]: def module_name_to_filename(self, module_name: str) -> str: raise EnvironmentException(f'{self.id} does not implement module_name_to_filename') + def get_extra_order_deps(self, target: BuildTarget) -> T.Iterable[BuildTargetTypes]: + return [] + def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: """Arguments to pass the compiler and/or linker for checks. diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 088551872259..86f69cb6a499 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -5,6 +5,7 @@ import typing as T import functools +import itertools import os from .. import options @@ -33,7 +34,7 @@ from ..environment import Environment from ..linkers.linkers import DynamicLinker from ..mesonlib import MachineChoice - from ..build import BuildTarget + from ..build import BuildTarget, BuildTargetTypes class FortranCompiler(CLikeCompiler, Compiler): @@ -81,6 +82,15 @@ def get_module_incdir_args(self) -> T.Tuple[str, ...]: def get_module_outdir_args(self, path: str) -> T.List[str]: return ['-module', path] + # Fortran is a bit weird (again). When you link against a library, just compiling a source file + # requires the mod files that are output when single files are built. To do this right we would need to + # scan all inputs and write out explicit deps for each file. That is too slow and too much effort so + # instead just have an ordered dependency on the library. This ensures all required mod files are created. + # The real deps are then detected via dep file generation from the compiler. This breaks on compilers that + # produce incorrect dep files but such is life. + def get_extra_order_deps(self, target: BuildTarget) -> T.Iterable[BuildTargetTypes]: + return itertools.chain(target.link_targets, target.link_whole_targets) + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: for idx, i in enumerate(parameter_list): diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index 3acc30e5458c..cbfc88516ec7 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -276,6 +276,8 @@ def get_colorout_args(self, colortype: str) -> T.List[str]: def get_linker_always_args(self) -> T.List[str]: args: T.List[str] = [] + # Rust is super annoying, calling -C link-arg foo does not work, it has + # to be -C link-arg=foo for a in super().get_linker_always_args(): args.extend(['-C', f'link-arg={a}']) return args diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 4b023a8aed69..1e5dcfb94534 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -3481,6 +3481,8 @@ def build_target(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargs target = targetclass(name, self.subdir, self.subproject, for_machine, srcs, struct, objs, self.environment, self.compilers[for_machine], kwargs) + if objs and target.uses_rust(): + FeatureNew.single_use('objects in Rust targets', '1.8.0', self.subproject) self.add_target(name, target) self.project_args_frozen = True diff --git a/test cases/rust/27 objects/lib1-dylib.rs b/test cases/rust/27 objects/lib1-dylib.rs new file mode 100644 index 000000000000..1dbf61422ea4 --- /dev/null +++ b/test cases/rust/27 objects/lib1-dylib.rs @@ -0,0 +1,15 @@ +extern "C" { + fn from_lib1(); +} + +#[no_mangle] +extern "C" fn from_lib2() +{ + println!("hello world from rust"); +} + +#[no_mangle] +pub extern "C" fn c_func() +{ + unsafe { from_lib1(); } +} diff --git a/test cases/rust/27 objects/lib1.c b/test cases/rust/27 objects/lib1.c new file mode 100644 index 000000000000..b463bffa3c6e --- /dev/null +++ b/test cases/rust/27 objects/lib1.c @@ -0,0 +1,11 @@ +#include +#include "lib1.h" +#include "lib2.h" + +void from_lib2(void) { + printf("hello world from c\n"); +} + +void c_func(void) { + from_lib1(); +} diff --git a/test cases/rust/27 objects/lib1.h b/test cases/rust/27 objects/lib1.h new file mode 100644 index 000000000000..8bb18d4bbe4e --- /dev/null +++ b/test cases/rust/27 objects/lib1.h @@ -0,0 +1,4 @@ +#pragma once + +void from_lib2(void); +void c_func(void); diff --git a/test cases/rust/27 objects/lib2.c b/test cases/rust/27 objects/lib2.c new file mode 100644 index 000000000000..a61d5349f878 --- /dev/null +++ b/test cases/rust/27 objects/lib2.c @@ -0,0 +1,8 @@ +#include +#include "lib1.h" +#include "lib2.h" + +void from_lib1(void) +{ + from_lib2(); +} diff --git a/test cases/rust/27 objects/lib2.h b/test cases/rust/27 objects/lib2.h new file mode 100644 index 000000000000..08c4cd30ad1e --- /dev/null +++ b/test cases/rust/27 objects/lib2.h @@ -0,0 +1,3 @@ +#pragma once + +void from_lib1(void); diff --git a/test cases/rust/27 objects/main.rs b/test cases/rust/27 objects/main.rs new file mode 100644 index 000000000000..538359943271 --- /dev/null +++ b/test cases/rust/27 objects/main.rs @@ -0,0 +1,9 @@ +extern "C" { + fn c_func(); +} + +fn main() { + unsafe { + c_func(); + } +} diff --git a/test cases/rust/27 objects/meson.build b/test cases/rust/27 objects/meson.build new file mode 100644 index 000000000000..78373e4b6535 --- /dev/null +++ b/test cases/rust/27 objects/meson.build @@ -0,0 +1,28 @@ +project('staticlib group', 'c', 'rust', meson_version: '>=1.8.0') + +lib1 = static_library('lib1', 'lib1.c') +dep1 = declare_dependency(objects: lib1.extract_all_objects(recursive: false)) +lib2 = static_library('lib2', 'lib2.c') +dep2 = declare_dependency(objects: lib2.extract_all_objects(recursive: false)) +executable('lib1objs', 'main.rs', + objects: lib1.extract_all_objects(recursive: false), + link_with: lib2) +executable('lib2objs', 'main.rs', + objects: lib2.extract_all_objects(recursive: false), + link_with: lib1) +executable('lib1objs_as_dep', 'main.rs', + dependencies: dep1, + link_with: lib2) +executable('lib2objs_as_dep', 'main.rs', + dependencies: dep2, + link_with: lib1) + +lib12 = shared_library('dylib2objs', 'lib1-dylib.rs', + objects: lib2.extract_all_objects(recursive: false), + rust_abi: 'c') +executable('dylib', 'main.rs', link_with: lib12) + +lib12 = shared_library('dylib2objs_as_dep', 'lib1-dylib.rs', + dependencies: dep2, + rust_abi: 'c') +executable('dylib_as_dep', 'main.rs', link_with: lib12)