Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for EnvironmentVariables in Rust targets #10030

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
34 changes: 31 additions & 3 deletions mesonbuild/backend/ninjabackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from ..linkers.linkers import DynamicLinker, StaticLinker
from ..compilers.cs import CsCompiler
from ..compilers.fortran import FortranCompiler
from ..compilers.rust import RustCompiler

CommandArgOrStr = T.List[T.Union['NinjaCommandArg', str]]
RUST_EDITIONS = Literal['2015', '2018', '2021']
Expand Down Expand Up @@ -1846,7 +1847,7 @@ def _get_rust_dependency_name(self, target: build.BuildTarget, dependency: LibTy
return target.rust_dependency_map.get(dependency.name, dependency.name).replace('-', '_')

def generate_rust_target(self, target: build.BuildTarget) -> None:
rustc = target.compilers['rust']
rustc = T.cast('RustCompiler', target.compilers['rust'])
# Rust compiler takes only the main file as input and
# figures out what other files are needed via import
# statements and magic.
Expand Down Expand Up @@ -2063,6 +2064,10 @@ def _link_library(libname: str, static: bool, bundle: bool = False):
for rpath_arg in rpath_args:
args += ['-C', 'link-arg=' + rpath_arg + ':' + os.path.join(rustc.get_sysroot(), 'lib')]

compiler_name: str = self.compiler_to_rule_name(rustc)
if rustc.needs_env_wrapper:
compiler_name = self.get_compiler_rule_name(rustc.get_language(), rustc.for_machine, 'ENV')

proc_macro_dylib_path = None
if getattr(target, 'rust_crate_type', '') == 'proc-macro':
proc_macro_dylib_path = os.path.abspath(os.path.join(target.subdir, target.get_filename()))
Expand All @@ -2074,12 +2079,29 @@ def _link_library(libname: str, static: bool, bundle: bool = False):
proc_macro_dylib_path,
project_deps)

compiler_name = self.compiler_to_rule_name(rustc)
element = NinjaBuildElement(self.all_outputs, target_name, compiler_name, main_rust_file)
if orderdeps:
element.add_orderdep(orderdeps)
if deps:
element.add_dep(deps)
if rustc.needs_env_wrapper:
env: T.List[str] = []
nargs: T.List[str] = []
itr = iter(args)
for a in itr:
if a == '--env-set':
a = next(itr)
elif a.startswith('--env-set='):
a = a.split('=', 1)[1]
else:
nargs.append(a)
continue
env.extend(['--env', a])
element.add_item('ENV', env)
args = nargs
else:
# XXX: this needs a heiristc
args.extend(['-Z', 'unstable-options'])
element.add_item('ARGS', args)
element.add_item('targetdep', depfile)
element.add_item('cratetype', cratetype)
Expand Down Expand Up @@ -2366,14 +2388,20 @@ def generate_cython_compile_rules(self, compiler: 'Compiler') -> None:
depfile=depfile,
extra='restat = 1'))

def generate_rust_compile_rules(self, compiler):
def generate_rust_compile_rules(self, compiler: RustCompiler) -> None:
rule = self.compiler_to_rule_name(compiler)
command = compiler.get_exelist() + ['$ARGS', '$in']
description = 'Compiling Rust source $in'
depfile = '$targetdep'
depstyle = 'gcc'
self.add_rule(NinjaRule(rule, command, [], description, deps=depstyle,
depfile=depfile))
if compiler.needs_env_wrapper:
rule = self.get_compiler_rule_name(compiler.get_language(), compiler.for_machine, 'ENV')
command = self.environment.get_build_command() + ['--internal', 'exe', '$ENV', '--'] + command
description = f'{description} (wrapped to set environment variables)'
self.add_rule(NinjaRule(rule, command, [], description, deps=depstyle,
depfile=depfile))

def generate_swift_compile_rules(self, compiler):
rule = self.compiler_to_rule_name(compiler)
Expand Down
3 changes: 2 additions & 1 deletion mesonbuild/compilers/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,7 @@ def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> Rust
continue

version = search_version(out)
is_nightly = 'nightly' in out
cls: T.Type[RustCompiler] = rust.RustCompiler

# Clippy is a wrapper around rustc, but it doesn't have rustc in it's
Expand Down Expand Up @@ -1078,7 +1079,7 @@ def detect_rust_compiler(env: 'Environment', for_machine: MachineChoice) -> Rust
env.coredata.add_lang_args(cls.language, cls, for_machine, env)
return cls(
compiler, version, for_machine, is_cross, info, exe_wrap,
linker=linker)
linker=linker, is_nightly=is_nightly)

_handle_exceptions(popen_exceptions, compilers)
raise EnvironmentException('Unreachable code (exception to make mypy happy)')
Expand Down
7 changes: 5 additions & 2 deletions mesonbuild/compilers/rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import typing as T

from .. import coredata
from ..mesonlib import EnvironmentException, MesonException, Popen_safe_logged, OptionKey
from ..mesonlib import EnvironmentException, MesonException, Popen_safe_logged, OptionKey, version_compare
from .compilers import Compiler, clike_debug_args

if T.TYPE_CHECKING:
Expand Down Expand Up @@ -60,7 +60,8 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic
is_cross: bool, info: 'MachineInfo',
exe_wrapper: T.Optional['ExternalProgram'] = None,
full_version: T.Optional[str] = None,
linker: T.Optional['DynamicLinker'] = None):
linker: T.Optional['DynamicLinker'] = None,
is_nightly: bool = False):
super().__init__([], exelist, version, for_machine, info,
is_cross=is_cross, full_version=full_version,
linker=linker)
Expand All @@ -69,6 +70,8 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic
if 'link' in self.linker.id:
self.base_options.add(OptionKey('b_vscrt'))
self.native_static_libs: T.List[str] = []
self.is_nightly = is_nightly
self.needs_env_wrapper = not (version_compare(self.version, '>= 1.76') and self.is_nightly)

def needs_static_linker(self) -> bool:
return False
Expand Down
22 changes: 20 additions & 2 deletions mesonbuild/scripts/meson_exe.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,25 @@
import typing as T
import locale

from ..utils.core import ExecutableSerialisation
from ..utils.core import ExecutableSerialisation, EnvironmentVariables

if T.TYPE_CHECKING:
from typing_extensions import Protocol

class Args(Protocol):

unpickle: bool
capture: bool
feed: bool
env: T.List[str]


def buildparser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description='Custom executable wrapper for Meson. Do not run on your own, mmm\'kay?')
parser.add_argument('--unpickle')
parser.add_argument('--capture')
parser.add_argument('--feed')
parser.add_argument('--env', action='append', default=[])
return parser

def run_exe(exe: ExecutableSerialisation, extra_env: T.Optional[T.Dict[str, str]] = None) -> int:
Expand Down Expand Up @@ -93,6 +105,7 @@ def run_exe(exe: ExecutableSerialisation, extra_env: T.Optional[T.Dict[str, str]

def run(args: T.List[str]) -> int:
parser = buildparser()
options: Args
options, cmd_args = parser.parse_known_args(args)
# argparse supports double dash to separate options and positional arguments,
# but the user has to remove it manually.
Expand All @@ -107,7 +120,12 @@ def run(args: T.List[str]) -> int:
exe = pickle.load(f)
exe.pickled = True
else:
exe = ExecutableSerialisation(cmd_args, capture=options.capture, feed=options.feed)
env = EnvironmentVariables()
for e in options.env:
name, value = e.split('=')
# This is internal, we can pick whatever separator we want!
env.append(name, value.split(':'))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not so sure, that's going to split C:\foo on Windows. Also you should not assume user wants to append to current env, they could want to set() or prepend(). I think for that you should pickle an ExecutableSerialisation() that contains the original EnvironmentVariables() object, instead of adding --env in the command line.

exe = ExecutableSerialisation(cmd_args, capture=options.capture, feed=options.feed, env=env)

return run_exe(exe)

Expand Down
21 changes: 21 additions & 0 deletions test cases/rust/23 env/generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env python3

import argparse
import os
import textwrap


def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument('outdir')
args = parser.parse_args()

with open(os.path.join(args.outdir, 'generated.rs'), 'w') as f:
f.write(textwrap.dedent('''\
fn generated() {
std::process::exit(0);
}'''))


if __name__ == '__main__':
main()
5 changes: 5 additions & 0 deletions test cases/rust/23 env/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
include!(concat!(env!("OUT_DIR"), "/generated.rs"));

fn main() {
generated();
}
16 changes: 16 additions & 0 deletions test cases/rust/23 env/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
project('rust environment variables', 'rust')

gen = find_program('generator.py')

ct = custom_target(
'generated.rs',
output : 'generated.rs',
command : [gen, '@OUTDIR@'],
)

exe = executable('main', 'main.rs', ct, rust_args: ['--env-set', 'OUT_DIR=' + meson.current_build_dir()])

test('main test', exe)

# test that generated and non-generated work together
executable('static', 'static.rs')
3 changes: 3 additions & 0 deletions test cases/rust/23 env/static.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
std::process::exit(0);
}
Loading