diff --git a/mesonbuild/cargo/interpreter.py b/mesonbuild/cargo/interpreter.py index f8f73a3b2ef3..9bf9a9cb3438 100644 --- a/mesonbuild/cargo/interpreter.py +++ b/mesonbuild/cargo/interpreter.py @@ -23,7 +23,7 @@ from . import builder from . import version -from ..mesonlib import MesonException, Popen_safe +from ..mesonlib import MesonException, Popen_safe, File, MachineChoice from .. import coredata, mlog from ..wrap.wrap import PackageDefinition @@ -34,6 +34,7 @@ from .. import mparser from ..environment import Environment from ..interpreterbase import SubProject + from ..compilers.rust import RustCompiler # tomllib is present in python 3.11, before that it is a pypi module called tomli, # we try to import tomllib, then tomli, @@ -422,7 +423,7 @@ def __init__(self, env: Environment) -> None: # Map of cargo package (name + api) to its state self.packages: T.Dict[PackageKey, PackageState] = {} - def interpret(self, subdir: str) -> mparser.CodeBlockNode: + def interpret(self, subdir: str) -> T.Tuple[mparser.CodeBlockNode, T.List[str]]: manifest = self._load_manifest(subdir) pkg, cached = self._fetch_package(manifest.package.name, manifest.package.api) if not cached: @@ -430,8 +431,15 @@ def interpret(self, subdir: str) -> mparser.CodeBlockNode: # FIXME: We should have a Meson option similar to `cargo build --no-default-features` self._enable_feature(pkg, 'default') + build_rs_args: T.List[str] = [] + build_def_files: T.List[str] = [] + has_meson_subdir = os.path.isfile(os.path.join(self.environment.source_dir, subdir, 'meson', 'meson.build')) + if not has_meson_subdir: + build_rs_args, build_def_files = self._run_build_rs(pkg, subdir) + # Build an AST for this package filename = os.path.join(self.environment.source_dir, subdir, 'Cargo.toml') + build_def_files.append(filename) build = builder.Builder(filename) ast = self._create_project(pkg, build) ast += [ @@ -440,9 +448,10 @@ def interpret(self, subdir: str) -> mparser.CodeBlockNode: build.string('Enabled features:'), build.array([build.string(f) for f in pkg.features]), ]), + build.assign(build.array([build.string(i) for i in build_rs_args]), 'build_rs_args'), ] ast += self._create_dependencies(pkg, build) - ast += self._create_meson_subdir(build) + ast += self._create_meson_subdir(build, has_meson_subdir) # Libs are always auto-discovered and there's no other way to handle them, # which is unfortunate for reproducability @@ -450,7 +459,7 @@ def interpret(self, subdir: str) -> mparser.CodeBlockNode: for crate_type in pkg.manifest.lib.crate_type: ast.extend(self._create_lib(pkg, build, crate_type)) - return build.block(ast) + return build.block(ast), build_def_files def _fetch_package(self, package_name: str, api: str) -> T.Tuple[PackageState, bool]: key = PackageKey(package_name, api) @@ -531,6 +540,82 @@ def _enable_feature(self, pkg: PackageState, feature: str) -> None: else: self._enable_feature(pkg, f) + CARGO_CFG_PREFIX = 'cargo:rustc-cfg=' + CARGO_RERUN_PREFIX = 'cargo:rerun-if-changed=' + + def _run_build_rs(self, pkg: PackageState, subdir: str) -> T.Tuple[T.List[str], T.List[str]]: + build_rs = os.path.join(self.environment.source_dir, subdir, 'build.rs') + if not os.path.exists(build_rs): + return [], [] + + if pkg.manifest.build_dependencies: + # FIXME: This should usually be fatal, but not always. For example + # the build.rs could be using system-deps and Meson provides the + # same functionality without build.rs. We have no way to know, + # print a warning for now. + mlog.warning('Cannot use build.rs with build-dependencies. It should be ported manually in meson/meson.build.') + return [], [] + + build_rustc = T.cast('RustCompiler', self.environment.coredata.compilers[MachineChoice.BUILD]['rust']) + host_rustc = T.cast('RustCompiler', self.environment.coredata.compilers[MachineChoice.HOST]['rust']) + + # Prepare build.rs run environment + # https://doc.rust-lang.org/cargo/reference/environment-variables.html + host_rustc_cmd = host_rustc.get_exelist(ccache=False) + out_dir = os.path.join(self.environment.build_dir, subdir, 'build.rs.p') + version_arr = pkg.manifest.package.version.split('.') + version_arr += ['' * (4 - len(version_arr))] + env = os.environ.copy() + env.update({ + 'OUT_DIR': out_dir, + 'RUSTC': host_rustc_cmd[0], + 'TARGET': host_rustc.get_target_triplet(), + 'CARGO_MANIFEST_DIR': os.path.join(self.environment.source_dir, subdir), + 'CARGO_PKG_VERSION': pkg.manifest.package.version, + 'CARGO_PKG_VERSION_MAJOR': version_arr[0], + 'CARGO_PKG_VERSION_MINOR': version_arr[1], + 'CARGO_PKG_VERSION_PATCH': version_arr[2], + 'CARGO_PKG_VERSION_PRE': version_arr[3], + 'CARGO_PKG_AUTHORS': ','.join(pkg.manifest.package.authors), + 'CARGO_PKG_NAME': pkg.manifest.package.name, + 'CARGO_PKG_DESCRIPTION': pkg.manifest.package.description or '', + 'CARGO_PKG_HOMEPAGE': pkg.manifest.package.homepage or '', + 'CARGO_PKG_REPOSITORY': pkg.manifest.package.repository or '', + 'CARGO_PKG_LICENSE': pkg.manifest.package.license or '', + 'CARGO_PKG_LICENSE_FILE': pkg.manifest.package.license_file or '', + 'CARGO_PKG_RUST_VERSION': pkg.manifest.package.rust_version or '', + 'CARGO_PKG_README': pkg.manifest.package.readme or '', + }) + def conv(k: str) -> str: + return k.upper().replace('-', '_') + # TODO: Add cfgs + #for k, v in self.cfgs.items(): + # env[f'CARGO_CFG_{conv(k)}'] = v + for f in pkg.features: + env[f'CARGO_FEATURE_{conv(f)}'] = '' + + # Compile and run build.rs + build_rs_file = File.from_absolute_file(build_rs) + cwd = os.path.join(self.environment.source_dir, subdir) + res = build_rustc.run(build_rs_file, self.environment, run_env=env, run_cwd=cwd) + if res.returncode != 0: + raise MesonException('Could not run build.rs') + mlog.log('Run ', mlog.bold('build.rs'), ': ', mlog.green('YES'), sep='') + + # Parse stdout to get configs and list of files that needs to trigger a + # reconfigure. + compiler_args: T.List[str] = [] + build_def_files: T.List[str] = [build_rs] + for line in res.stdout.splitlines(): + if line.startswith(self.CARGO_CFG_PREFIX): + cfg = line[len(self.CARGO_CFG_PREFIX):] + compiler_args += ['--cfg', cfg] + elif line.startswith(self.CARGO_RERUN_PREFIX): + path = line[len(self.CARGO_RERUN_PREFIX):] + build_def_files.append(os.path.join(self.environment.source_dir, subdir, path)) + + return compiler_args, build_def_files + def _create_project(self, pkg: PackageState, build: builder.Builder) -> T.List[mparser.BaseNode]: """Create the project() function call @@ -626,23 +711,19 @@ def _create_dependency(self, dep: Dependency, build: builder.Builder) -> T.List[ ])), ] - def _create_meson_subdir(self, build: builder.Builder) -> T.List[mparser.BaseNode]: + def _create_meson_subdir(self, build: builder.Builder, has_meson_subdir: bool) -> T.List[mparser.BaseNode]: # Allow Cargo subprojects to add extra Rust args in meson/meson.build file. # This is used to replace build.rs logic. - # extra_args = [] # extra_deps = [] - # fs = import('fs') - # if fs.is_dir('meson') - # subdir('meson') - # endif - return [ + # subdir('meson') + ast: T.List[mparser.BaseNode] = [ build.assign(build.array([]), _extra_args_varname()), build.assign(build.array([]), _extra_deps_varname()), - build.assign(build.function('import', [build.string('fs')]), 'fs'), - build.if_(build.method('is_dir', build.identifier('fs'), [build.string('meson')]), - build.block([build.function('subdir', [build.string('meson')])])) ] + if has_meson_subdir: + ast.append(build.function('subdir', [build.string('meson')])) + return ast def _create_lib(self, pkg: PackageState, build: builder.Builder, crate_type: manifest.CRATE_TYPE) -> T.List[mparser.BaseNode]: dependencies: T.List[mparser.BaseNode] = [] @@ -657,6 +738,7 @@ def _create_lib(self, pkg: PackageState, build: builder.Builder, crate_type: man rust_args: T.List[mparser.BaseNode] = [ build.identifier('features_args'), + build.identifier('build_rs_args'), build.identifier(_extra_args_varname()) ] diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 778afaee19e1..7b2001f90e23 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -1044,13 +1044,14 @@ def _do_subproject_cargo(self, subp_name: str, subdir: str, from .. import cargo FeatureNew.single_use('Cargo subproject', '1.3.0', self.subproject, location=self.current_node) if self.environment.cargo is None: + self.add_languages(['rust'], True, MachineChoice.BUILD) + self.add_languages(['rust'], True, MachineChoice.HOST) self.environment.cargo = cargo.Interpreter(self.environment) with mlog.nested(subp_name): - ast = self.environment.cargo.interpret(subdir) + ast, build_def_files = self.environment.cargo.interpret(subdir) return self._do_subproject_meson( subp_name, subdir, default_options, kwargs, ast, - # FIXME: Are there other files used by cargo interpreter? - [os.path.join(subdir, 'Cargo.toml')]) + build_def_files) def get_option_internal(self, optname: str) -> options.UserOption: key = OptionKey.from_string(optname).evolve(subproject=self.subproject) diff --git a/test cases/rust/27 cargo build.rs/meson.build b/test cases/rust/27 cargo build.rs/meson.build new file mode 100644 index 000000000000..4094759f03f7 --- /dev/null +++ b/test cases/rust/27 cargo build.rs/meson.build @@ -0,0 +1,3 @@ +project('Cargo build.rs') + +dependency('foo-0-rs') diff --git a/test cases/rust/27 cargo build.rs/subprojects/foo-0-rs.wrap b/test cases/rust/27 cargo build.rs/subprojects/foo-0-rs.wrap new file mode 100644 index 000000000000..5133599d29df --- /dev/null +++ b/test cases/rust/27 cargo build.rs/subprojects/foo-0-rs.wrap @@ -0,0 +1,2 @@ +[wrap-file] +method=cargo diff --git a/test cases/rust/27 cargo build.rs/subprojects/foo-0-rs/Cargo.toml b/test cases/rust/27 cargo build.rs/subprojects/foo-0-rs/Cargo.toml new file mode 100644 index 000000000000..5b17517ef8d2 --- /dev/null +++ b/test cases/rust/27 cargo build.rs/subprojects/foo-0-rs/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "foo" +version = "0.0.1" + +[lib] +path = "lib.rs" diff --git a/test cases/rust/27 cargo build.rs/subprojects/foo-0-rs/build.rs b/test cases/rust/27 cargo build.rs/subprojects/foo-0-rs/build.rs new file mode 100644 index 000000000000..1d963e67ec79 --- /dev/null +++ b/test cases/rust/27 cargo build.rs/subprojects/foo-0-rs/build.rs @@ -0,0 +1,8 @@ +use std::env; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rustc-cfg=foo"); + println!("cargo:rustc-cfg=bar=\"val\""); + assert!(env::var("CARGO_FEATURE_DEFAULT").is_ok()); +} diff --git a/test cases/rust/27 cargo build.rs/subprojects/foo-0-rs/lib.rs b/test cases/rust/27 cargo build.rs/subprojects/foo-0-rs/lib.rs new file mode 100644 index 000000000000..e6eb6a3d8f8b --- /dev/null +++ b/test cases/rust/27 cargo build.rs/subprojects/foo-0-rs/lib.rs @@ -0,0 +1,9 @@ +#[cfg(foo)] +#[cfg(bar="val")] +pub fn func1() -> i32 { + 42 +} + +pub fn func2() -> i32 { + func1() +}