Skip to content

Commit

Permalink
cargo: Run build.rs to get extra --cfg args
Browse files Browse the repository at this point in the history
  • Loading branch information
xclaesse committed Oct 11, 2024
1 parent fc1fbce commit ed2e434
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 17 deletions.
110 changes: 96 additions & 14 deletions mesonbuild/cargo/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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,
Expand Down Expand Up @@ -422,16 +423,23 @@ 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:
# This is an entry point, always enable the 'default' feature.
# 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 += [
Expand All @@ -440,17 +448,18 @@ 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
if os.path.exists(os.path.join(self.environment.source_dir, subdir, pkg.manifest.path, pkg.manifest.lib.path)):
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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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] = []
Expand All @@ -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())
]

Expand Down
7 changes: 4 additions & 3 deletions mesonbuild/interpreter/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions test cases/rust/27 cargo build.rs/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
project('Cargo build.rs')

dependency('foo-0-rs')
2 changes: 2 additions & 0 deletions test cases/rust/27 cargo build.rs/subprojects/foo-0-rs.wrap
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[wrap-file]
method=cargo
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "foo"
version = "0.0.1"

[lib]
path = "lib.rs"
Original file line number Diff line number Diff line change
@@ -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());
}
9 changes: 9 additions & 0 deletions test cases/rust/27 cargo build.rs/subprojects/foo-0-rs/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#[cfg(foo)]
#[cfg(bar="val")]
pub fn func1() -> i32 {
42
}

pub fn func2() -> i32 {
func1()
}

0 comments on commit ed2e434

Please sign in to comment.