Skip to content

Commit

Permalink
Merge 1dd23ab into 57c8e1a
Browse files Browse the repository at this point in the history
  • Loading branch information
dcbaker authored Feb 24, 2022
2 parents 57c8e1a + 1dd23ab commit 041b2d5
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 11 deletions.
22 changes: 22 additions & 0 deletions docs/markdown/snippets/rust_env_variables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## Support for passing environment variables to Rust targets

Rustc idiomatically uses environment variables in macros, such as with
`include!`:

```rust
include!(concat!(env!("OUT_DIR"), "/generated.rs"))
```

Meson will need to support this to effectively wrap cargo dependencies, and for
idiomatic Rust usage. This may now be done with the `env` keyword argument,
which works like the one to `custom_target`:

```meson
ct = custom_target('foo', command : ['foo', '--outdir', '@OUTDIR@'])
executable(
'main'
'main.rs',
env : {'OUT_DIR' : meson.current_build_dir()}
)
```
11 changes: 11 additions & 0 deletions docs/yaml/functions/_build_target_base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,14 @@ kwargs:
version specification such as `windows,6.0`. See [MSDN
documentation](https://docs.microsoft.com/en-us/cpp/build/reference/subsystem-specify-subsystem)
for the full list.
env:
type: env | list[str] | dict[str]
since: 0.62.0
description: |
environment variables to set, such as
`{'NAME1': 'value1', 'NAME2': 'value2'}` or `['NAME1=value1', 'NAME2=value2']`,
or an [[@env]] object which allows more sophisticated environment juggling.
This is only allowed for targets using Rust (currently), as rustc idiomatically
uses environment variables in macros.
31 changes: 24 additions & 7 deletions mesonbuild/backend/ninjabackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,7 @@ def unwrap_dep_list(self, target):
deps.append(os.path.join(self.get_target_dir(i), output))
return deps

def generate_custom_target(self, target):
def generate_custom_target(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]) -> None:
self.custom_target_generator_inputs(target)
(srcs, ofilenames, cmd) = self.eval_custom_target_command(target)
deps = self.unwrap_dep_list(target)
Expand Down Expand Up @@ -1785,7 +1785,7 @@ def generate_rust_target(self, target: build.BuildTarget) -> None:
# installations
for rpath_arg in rpath_args:
args += ['-C', 'link-arg=' + rpath_arg + ':' + os.path.join(rustc.get_sysroot(), 'lib')]
compiler_name = self.get_compiler_rule_name('rust', target.for_machine)
compiler_name = self.get_compiler_rule_name('rust', target.for_machine, bool(target.env))
element = NinjaBuildElement(self.all_outputs, target_name, compiler_name, main_rust_file)
if orderdeps:
element.add_orderdep(orderdeps)
Expand All @@ -1794,6 +1794,11 @@ def generate_rust_target(self, target: build.BuildTarget) -> None:
element.add_item('ARGS', args)
element.add_item('targetdep', depfile)
element.add_item('cratetype', cratetype)
if target.env:
env: T.List[str] = []
for k, v in target.env.get_env({}).items():
env.extend(['--env', f'{k}={v}'])
element.add_item('ENV', env)
self.add_build(element)
if isinstance(target, build.SharedLibrary):
self.generate_shsym(target)
Expand All @@ -1804,8 +1809,8 @@ def get_rule_suffix(for_machine: MachineChoice) -> str:
return PerMachine('_FOR_BUILD', '')[for_machine]

@classmethod
def get_compiler_rule_name(cls, lang: str, for_machine: MachineChoice) -> str:
return '{}_COMPILER{}'.format(lang, cls.get_rule_suffix(for_machine))
def get_compiler_rule_name(cls, lang: str, for_machine: MachineChoice, env: bool = False) -> str:
return '{}_COMPILER{}{}'.format(lang, cls.get_rule_suffix(for_machine), '_ENV' if env else '')

@classmethod
def get_pch_rule_name(cls, lang: str, for_machine: MachineChoice) -> str:
Expand Down Expand Up @@ -2050,14 +2055,26 @@ def generate_cython_compile_rules(self, compiler: 'Compiler') -> None:
description = 'Compiling Cython source $in'
self.add_rule(NinjaRule(rule, command, [], description, extra='restat = 1'))

def generate_rust_compile_rules(self, compiler):
def generate_rust_compile_rules(self, compiler: 'compilers.RustCompiler'):
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))
self.add_rule(NinjaRule(
rule,
command,
[],
description,
deps=depstyle,
depfile=depfile))
self.add_rule(NinjaRule(
f'{rule}_ENV',
self.environment.get_build_command() + ['--internal', 'exe', '$ENV', '--'] + command,
[],
f'{description} (with environment wrapper)',
deps=depstyle,
depfile=depfile))

def generate_swift_compile_rules(self, compiler):
rule = self.compiler_to_rule_name(compiler)
Expand Down
7 changes: 7 additions & 0 deletions mesonbuild/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
'gnu_symbol_visibility',
'link_language',
'win_subsystem',
'env',
}

known_build_target_kwargs = (
Expand Down Expand Up @@ -465,6 +466,9 @@ def __repr__(self) -> str:
repr_str = "<{0}: {1}>"
return repr_str.format(self.__class__.__name__, self.envvars)

def __bool__(self) -> bool:
return bool(self.envvars)

def hash(self, hasher: T.Any):
myenv = self.get_env({})
for key in sorted(myenv.keys()):
Expand Down Expand Up @@ -694,6 +698,7 @@ def __init__(self, name: str, subdir: str, subproject: 'SubProject', for_machine
self.name_prefix_set = False
self.name_suffix_set = False
self.filename = 'no_name'
self.env = EnvironmentVariables()
# The list of all files outputted by this target. Useful in cases such
# as Vala which generates .vapi and .h besides the compiled output.
self.outputs = [self.filename]
Expand Down Expand Up @@ -1050,6 +1055,8 @@ def process_kwargs(self, kwargs, environment):
self.process_kwargs_base(kwargs)
self.copy_kwargs(kwargs)
kwargs.get('modules', [])
if kwargs.get('env'):
self.env = EnvironmentVariables(kwargs['env'])
self.need_install = kwargs.get('install', self.need_install)
llist = extract_as_list(kwargs, 'link_with')
for linktarget in llist:
Expand Down
11 changes: 10 additions & 1 deletion mesonbuild/interpreter/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1608,26 +1608,31 @@ def func_disabler(self, node, args, kwargs):

@FeatureNewKwargs('executable', '0.42.0', ['implib'])
@FeatureNewKwargs('executable', '0.56.0', ['win_subsystem'])
@FeatureNewKwargs('executable', '0.62.0', ['env'])
@FeatureDeprecatedKwargs('executable', '0.56.0', ['gui_app'], extra_message="Use 'win_subsystem' instead.")
@permittedKwargs(build.known_exe_kwargs)
def func_executable(self, node, args, kwargs):
return self.build_target(node, args, kwargs, build.Executable)

@permittedKwargs(build.known_stlib_kwargs)
@FeatureNewKwargs('static_library', '0.62.0', ['env'])
def func_static_lib(self, node, args, kwargs):
return self.build_target(node, args, kwargs, build.StaticLibrary)

@permittedKwargs(build.known_shlib_kwargs)
@FeatureNewKwargs('shared_library', '0.62.0', ['env'])
def func_shared_lib(self, node, args, kwargs):
holder = self.build_target(node, args, kwargs, build.SharedLibrary)
holder.shared_library_only = True
return holder

@permittedKwargs(known_library_kwargs)
@FeatureNewKwargs('both_libraries', '0.62.0', ['env'])
def func_both_lib(self, node, args, kwargs):
return self.build_both_libraries(node, args, kwargs)

@FeatureNew('shared_module', '0.37.0')
@FeatureNewKwargs('shared_module', '0.62.0', ['env'])
@permittedKwargs(build.known_shmod_kwargs)
def func_shared_module(self, node, args, kwargs):
return self.build_target(node, args, kwargs, build.SharedModule)
Expand All @@ -1641,6 +1646,7 @@ def func_jar(self, node, args, kwargs):
return self.build_target(node, args, kwargs, build.Jar)

@FeatureNewKwargs('build_target', '0.40.0', ['link_whole', 'override_options'])
@FeatureNewKwargs('build_target', '0.62.0', ['env'])
@permittedKwargs(known_build_target_kwargs)
def func_build_target(self, node, args, kwargs):
if 'target_type' not in kwargs:
Expand Down Expand Up @@ -2806,7 +2812,8 @@ def build_library(self, node, args, kwargs):
else:
raise InterpreterException(f'Unknown default_library value: {default_library}.')

def build_target(self, node, args, kwargs, targetclass):
def build_target(self, node: mparser.BaseNode, args: T.List[TYPE_var], kwargs: TYPE_kwargs,
targetclass: T.Type[build.BuildTarget]) -> build.BuildTarget:
@FeatureNewKwargs('build target', '0.42.0', ['rust_crate_type', 'build_rpath', 'implicit_include_directories'])
@FeatureNewKwargs('build target', '0.41.0', ['rust_args'])
@FeatureNewKwargs('build target', '0.40.0', ['build_by_default'])
Expand Down Expand Up @@ -2842,6 +2849,8 @@ def build_target_decorator_caller(self, node, args, kwargs):
kwargs['include_directories'] = self.extract_incdirs(kwargs)
target = targetclass(name, self.subdir, self.subproject, for_machine, sources, objs, self.environment, kwargs)
target.project_version = self.project_version
if target.env and not target.uses_rust():
raise InvalidArguments('build_target: the env flag is only allowed with rust targets')

self.add_stdlib_info(target)
self.add_target(name, target)
Expand Down
23 changes: 20 additions & 3 deletions mesonbuild/scripts/meson_exe.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations
import os
import sys
import argparse
Expand All @@ -21,15 +22,26 @@
import locale

from .. import mesonlib
from .. import build
from ..backend.backends import ExecutableSerialisation

options = None
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 @@ -101,8 +113,8 @@ def run_exe(exe: ExecutableSerialisation, extra_env: T.Optional[T.Dict[str, str]
return 0

def run(args: T.List[str]) -> int:
global options
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 @@ -117,7 +129,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 = build.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(':'))
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/17 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/17 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/17 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, env: {'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/17 env/static.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
std::process::exit(0);
}

0 comments on commit 041b2d5

Please sign in to comment.