Merge pull request #9339 from dcbaker/submit/structured_sources

Structured Sources
pull/10117/head
Jussi Pakkanen 3 years ago committed by GitHub
commit 69ade4f4cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      data/syntax-highlighting/vim/syntax/meson.vim
  2. 56
      docs/markdown/Rust.md
  3. 26
      docs/markdown/snippets/structured_sources.md
  4. 1
      docs/sitemap.txt
  5. 2
      docs/yaml/functions/_build_target_base.yaml
  6. 21
      docs/yaml/functions/structured_sources.yaml
  7. 3
      docs/yaml/objects/structured_src.yaml
  8. 1
      mesonbuild/ast/interpreter.py
  9. 2
      mesonbuild/ast/introspection.py
  10. 55
      mesonbuild/backend/ninjabackend.py
  11. 132
      mesonbuild/build.py
  12. 9
      mesonbuild/dependencies/base.py
  13. 66
      mesonbuild/interpreter/interpreter.py
  14. 6
      mesonbuild/interpreter/interpreterobjects.py
  15. 10
      mesonbuild/mesonlib/universal.py
  16. 16
      mesonbuild/modules/gnome.py
  17. 11
      mesonbuild/modules/unstable_rust.py
  18. 18
      mesonbuild/scripts/copy.py
  19. 0
      test cases/failing/121 structured source empty string/main.rs
  20. 13
      test cases/failing/121 structured source empty string/meson.build
  21. 7
      test cases/failing/121 structured source empty string/test.json
  22. 0
      test cases/failing/122 structured_sources conflicts/main.rs
  23. 17
      test cases/failing/122 structured_sources conflicts/meson.build
  24. 7
      test cases/failing/122 structured_sources conflicts/test.json
  25. 20
      test cases/rust/18 structured sources/gen.py
  26. 39
      test cases/rust/18 structured sources/meson.build
  27. 18
      test cases/rust/18 structured sources/no_copy_test.py
  28. 4
      test cases/rust/18 structured sources/src/foo.rs.in
  29. 5
      test cases/rust/18 structured sources/src/main.rs
  30. 4
      test cases/rust/18 structured sources/src2/foo/mod.rs
  31. 5
      test cases/rust/18 structured sources/src2/main-unique.rs
  32. 1
      unittests/allplatformstests.py

@ -116,6 +116,7 @@ syn keyword mesonBuiltin
\ shared_library
\ shared_module
\ static_library
\ structured_sources
\ subdir
\ subdir_done
\ subproject

@ -0,0 +1,56 @@
---
title: Rust
short-description: Working with Rust in Meson
---
# Using Rust with Meson
## Mixing Rust and non-Rust sources
Meson currently does not support creating a single target with Rust and non Rust
sources mixed together, therefore one must compile multiple libraries and link
them.
```meson
rust_lib = static_library(
'rust_lib',
sources : 'lib.rs',
...
)
c_lib = static_library(
'c_lib',
sources : 'lib.c',
link_with : rust_lib,
)
```
This is an implementation detail of Meson, and is subject to change in the future.
## Mixing Generated and Static sources
*Note* This feature was added in 0.62
You can use a [[structured_source]] for this. Structured sources are a dictionary
mapping a string of the directory, to a source or list of sources.
When using a structured source all inputs *must* be listed, as Meson may copy
the sources from the source tree to the build tree.
Structured inputs are generally not needed when not using generated sources.
As an implementation detail, Meson will attempt to determine if it needs to copy
files at configure time and will skip copying if it can. Copying is done at
build time (when necessary), to avoid reconfiguring when sources change.
```meson
executable(
'rust_exe',
structured_sources(
'main.rs',
{
'foo' : ['bar.rs', 'foo/lib.rs', generated_rs],
'foo/bar' : [...],
'other' : [...],
}
)
)
```

@ -0,0 +1,26 @@
## structured_sources()
A new function, `structured_sources()` has been added. This function allows
languages like Rust which depend on the filesystem layout at compile time to mix
generated and static sources.
```meson
executable(
'main',
structured_sources(
'main.rs,
{'mod' : generated_mod_rs},
)
)
```
Meson will then at build time copy the files into the build directory (if
necessary), so that the desired file structure is laid out, and compile that. In
this case:
```
root/
main.rs
mod/
mod.rs
```

@ -63,6 +63,7 @@ index.md
Vala.md
D.md
Cython.md
Rust.md
IDE-integration.md
Custom-build-targets.md
Build-system-converters.md

@ -49,7 +49,7 @@ kwargs:
eg: `cpp_args` for C++
sources:
type: str | file | custom_tgt | custom_idx | generated_list
type: str | file | custom_tgt | custom_idx | generated_list | structured_src
description: Additional source files. Same as the source varargs.
build_by_default:

@ -0,0 +1,21 @@
name: structured_sources
returns: structured_src
since: 0.62.0
description: |
Create a StructuredSource object, which is opaque and may be passed as a source
to any build_target (including static_library, shared_library, executable,
etc.). This is useful for languages like Rust, which use the filesystem layout
to determine import names. This is only allowed in Rust targets, and cannot be
mixed with non structured inputs.
posargs:
root:
type: list[str | file | custom_tgt | custom_idx | generated_list]
description: Sources to put at the root of the generated structure
optargs:
additional:
type: dict[str | file | custom_tgt | custom_idx | generated_list]
description: |
Additional sources, where the key is the directory under the root to place
the values

@ -0,0 +1,3 @@
name: structured_src
long_name: Structured Source
description: Opaque object returned by [[structured_sources]].

@ -156,6 +156,7 @@ class AstInterpreter(InterpreterBase):
'alias_target': self.func_do_nothing,
'summary': self.func_do_nothing,
'range': self.func_do_nothing,
'structured_sources': self.func_do_nothing,
})
def _unholder_args(self, args: _T, kwargs: _V) -> T.Tuple[_T, _V]:

@ -249,7 +249,7 @@ class IntrospectionInterpreter(AstInterpreter):
objects = [] # type: T.List[T.Any]
empty_sources = [] # type: T.List[T.Any]
# Passing the unresolved sources list causes errors
target = targetclass(name, self.subdir, self.subproject, for_machine, empty_sources, objects, self.environment, kwargs_reduced)
target = targetclass(name, self.subdir, self.subproject, for_machine, empty_sources, [], objects, self.environment, kwargs_reduced)
new_target = {
'name': target.get_basename(),

@ -54,6 +54,7 @@ if T.TYPE_CHECKING:
from .._typing import ImmutableListProtocol
from ..linkers import DynamicLinker, StaticLinker
from ..compilers.cs import CsCompiler
from ..interpreter.interpreter import SourceOutputs
FORTRAN_INCLUDE_PAT = r"^\s*#?include\s*['\"](\w+\.\w+)['\"]"
@ -1176,6 +1177,8 @@ class NinjaBackend(backends.Backend):
self.add_rule(NinjaRule('CUSTOM_COMMAND_DEP', ['$COMMAND'], [], '$DESC',
deps='gcc', depfile='$DEPFILE',
extra='restat = 1'))
self.add_rule(NinjaRule('COPY_FILE', self.environment.get_build_command() + ['--internal', 'copy'],
['$in', '$out'], 'Copying $in to $out'))
c = self.environment.get_build_command() + \
['--internal',
@ -1650,6 +1653,39 @@ class NinjaBackend(backends.Backend):
return static_sources, generated_sources, cython_sources
def _generate_copy_target(self, src: 'mesonlib.FileOrString', output: Path) -> None:
"""Create a target to copy a source file from one location to another."""
if isinstance(src, File):
instr = src.absolute_path(self.environment.source_dir, self.environment.build_dir)
else:
instr = src
elem = NinjaBuildElement(self.all_outputs, [str(output)], 'COPY_FILE', [instr])
elem.add_orderdep(instr)
self.add_build(elem)
def __generate_compile_structure(self, target: build.BuildTarget) -> T.Tuple[T.List[str], T.Optional[str]]:
first_file: T.Optional[str] = None
orderdeps: T.List[str] = []
root = Path(self.get_target_private_dir(target)) / 'structured'
for path, files in target.structured_sources.sources.items():
for file in files:
if isinstance(file, (str, File)):
if isinstance(file, str):
file = File.from_absolute_file(file)
out = root / path / Path(file.fname).name
orderdeps.append(str(out))
self._generate_copy_target(file, out)
if first_file is None:
first_file = str(out)
else:
for f in file.get_outputs():
out = root / path / f
orderdeps.append(str(out))
self._generate_copy_target(str(Path(file.subdir) / f), out)
if first_file is None:
first_file = str(out)
return orderdeps, first_file
def generate_rust_target(self, target: build.BuildTarget) -> None:
rustc = target.compilers['rust']
# Rust compiler takes only the main file as input and
@ -1670,6 +1706,25 @@ class NinjaBackend(backends.Backend):
orderdeps: T.List[str] = []
main_rust_file = None
if target.structured_sources:
if target.structured_sources.needs_copy(target):
_ods, main_rust_file = self.__generate_compile_structure(target)
orderdeps.extend(_ods)
else:
g = target.structured_sources.first_file()
if isinstance(g, str):
g = File.from_source_file(self.environment.source_dir, target.subdir, g)
if isinstance(g, File):
main_rust_file = g.rel_to_builddir(self.build_to_src)
elif isinstance(g, GeneratedList):
main_rust_file = os.path.join(self.get_target_private_dir(target), i)
else:
main_rust_file = os.path.join(g.get_subdir(), i)
orderdeps.extend([os.path.join(self.build_to_src, target.subdir, s)
for s in target.structured_sources.as_list()])
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.')

@ -426,7 +426,7 @@ class ExtractedObjects(HoldableObject):
# Filter out headers and all non-source files
return [s for s in sources if environment.is_source(s) and not environment.is_header(s)]
def classify_all_sources(self, sources: T.List[str], generated_sources: T.Sequence['GeneratedTypes']) -> T.Dict['Compiler', T.List['FileOrString']]:
def classify_all_sources(self, sources: T.List[FileOrString], generated_sources: T.Sequence['GeneratedTypes']) -> T.Dict['Compiler', T.List['FileOrString']]:
sources_ = self.get_sources(sources, generated_sources)
return classify_unity_sources(self.target.compilers.values(), sources_)
@ -452,10 +452,65 @@ class ExtractedObjects(HoldableObject):
for source in self.get_sources(self.srclist, self.genlist)
]
@dataclass(eq=False, order=False)
class StructuredSources(HoldableObject):
"""A container for sources in languages that use filesystem hierarchy.
Languages like Rust and Cython rely on the layout of files in the filesystem
as part of the compiler implementation. This structure allows us to
represent the required filesystem layout.
"""
sources: T.DefaultDict[str, T.List[T.Union[str, File, CustomTarget, CustomTargetIndex, GeneratedList]]] = field(
default_factory=lambda: defaultdict(list))
def __add__(self, other: StructuredSources) -> StructuredSources:
sources = self.sources.copy()
for k, v in other.sources.items():
sources[k].extend(v)
return StructuredSources(sources)
def __bool__(self) -> bool:
return bool(self.sources)
def first_file(self) -> T.Union[str, File, CustomTarget, CustomTargetIndex, GeneratedList]:
"""Get the first source in the root
:return: The first source in the root
"""
return self.sources[''][0]
def as_list(self) -> T.List[T.Union[str, File, CustomTarget, CustomTargetIndex, GeneratedList]]:
return list(itertools.chain.from_iterable(self.sources.values()))
def needs_copy(self, target: BuildTarget) -> bool:
"""Do we need to create a structure in the build directory.
This allows us to avoid making copies if the structures exists in the
source dir. Which could happen in situations where a generated source
only exists in some configurations
"""
p = pathlib.Path(target.subdir)
for files in self.sources.values():
for f in files:
if isinstance(f, str):
if not (target.environment.source_dir / p / f).exists():
return True
elif isinstance(f, File):
if f.is_built:
return True
else:
return True
return False
EnvInitValueType = T.Dict[str, T.Union[str, T.List[str]]]
class EnvironmentVariables(HoldableObject):
def __init__(self, values: T.Optional[EnvValueType] = None,
def __init__(self, values: T.Optional[EnvInitValueType] = None,
init_method: Literal['set', 'prepend', 'append'] = 'set', separator: str = os.pathsep) -> None:
self.envvars: T.List[T.Tuple[T.Callable[[T.Dict[str, str], str, T.List[str], str], str], str, T.List[str], str]] = []
# The set of all env vars we have operations for. Only used for self.has_name()
@ -684,14 +739,16 @@ class BuildTarget(Target):
install_dir: T.List[T.Union[str, bool]]
def __init__(self, name: str, subdir: str, subproject: 'SubProject', for_machine: MachineChoice,
sources: T.List['SourceOutputs'], objects, environment: environment.Environment, kwargs):
def __init__(self, name: str, subdir: str, subproject: SubProject, for_machine: MachineChoice,
sources: T.List['SourceOutputs'], structured_sources: T.Optional[StructuredSources],
objects, environment: environment.Environment, kwargs):
super().__init__(name, subdir, subproject, True, for_machine)
unity_opt = environment.coredata.get_option(OptionKey('unity'))
self.is_unity = unity_opt == 'on' or (unity_opt == 'subprojects' and subproject != '')
self.environment = environment
self.compilers = OrderedDict() # type: OrderedDict[str, Compiler]
self.objects: T.List[T.Union[str, 'File', 'ExtractedObjects']] = []
self.structured_sources = structured_sources
self.external_deps: T.List[dependencies.Dependency] = []
self.include_dirs: T.List['IncludeDirs'] = []
self.link_language = kwargs.get('link_language')
@ -723,13 +780,18 @@ class BuildTarget(Target):
self.process_kwargs(kwargs, environment)
self.check_unknown_kwargs(kwargs)
self.process_compilers()
if not any([self.sources, self.generated, self.objects, self.link_whole]):
if not any([self.sources, self.generated, self.objects, self.link_whole, self.structured_sources]):
raise InvalidArguments(f'Build target {name} has no sources.')
self.process_compilers_late()
self.validate_sources()
self.validate_install(environment)
self.check_module_linking()
if self.structured_sources and any([self.sources, self.generated]):
raise MesonException('cannot mix structured sources and unstructured sources')
if self.structured_sources and 'rust' not in self.compilers:
raise MesonException('structured sources are only supported in Rust targets')
def __repr__(self):
repr_str = "<{0} {1}: {2}>"
return repr_str.format(self.__class__.__name__, self.get_id(), self.filename)
@ -833,21 +895,31 @@ class BuildTarget(Target):
self.compilers[lang] = compilers[lang]
break
def process_compilers(self):
def process_compilers(self) -> None:
'''
Populate self.compilers, which is the list of compilers that this
target will use for compiling all its sources.
We also add compilers that were used by extracted objects to simplify
dynamic linker determination.
'''
if not self.sources and not self.generated and not self.objects:
if not any([self.sources, self.generated, self.objects, self.structured_sources]):
return
# Populate list of compilers
compilers = self.environment.coredata.compilers[self.for_machine]
# Pre-existing sources
sources = list(self.sources)
sources: T.List['FileOrString'] = list(self.sources)
generated = self.generated.copy()
if self.structured_sources:
for v in self.structured_sources.sources.values():
for src in v:
if isinstance(src, (str, File)):
sources.append(src)
else:
generated.append(src)
# All generated sources
for gensrc in self.generated:
for gensrc in generated:
for s in gensrc.get_outputs():
# Generated objects can't be compiled, so don't use them for
# compiler detection. If our target only has generated objects,
@ -1571,6 +1643,16 @@ You probably should put it in link_with instead.''')
elif self.generated:
if self.generated[0].get_outputs()[0].endswith('.rs'):
return True
elif self.structured_sources:
for v in self.structured_sources.sources.values():
for s in v:
if isinstance(s, (str, File)):
if s.endswith('.rs'):
return True
else:
for ss in s.get_outputs():
if ss.endswith('.rs'):
return True
return False
def get_using_msvc(self) -> bool:
@ -1772,12 +1854,13 @@ class Executable(BuildTarget):
known_kwargs = known_exe_kwargs
def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice,
sources: T.List[File], objects, environment: environment.Environment, kwargs):
sources: T.List[File], structured_sources: T.Optional['StructuredSources'],
objects, environment: environment.Environment, kwargs):
self.typename = 'executable'
key = OptionKey('b_pie')
if 'pie' not in kwargs and key in environment.coredata.options:
kwargs['pie'] = environment.coredata.options[key].value
super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs)
super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, environment, kwargs)
# Unless overridden, executables have no suffix or prefix. Except on
# Windows and with C#/Mono executables where the suffix is 'exe'
if not hasattr(self, 'prefix'):
@ -1897,9 +1980,11 @@ class Executable(BuildTarget):
class StaticLibrary(BuildTarget):
known_kwargs = known_stlib_kwargs
def __init__(self, name, subdir, subproject, for_machine: MachineChoice, sources, objects, environment, kwargs):
def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice,
sources: T.List[File], structured_sources: T.Optional['StructuredSources'],
objects, environment: environment.Environment, kwargs):
self.typename = 'static library'
super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs)
super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, environment, kwargs)
if 'cs' in self.compilers:
raise InvalidArguments('Static libraries not supported for C#.')
if 'rust' in self.compilers:
@ -1958,7 +2043,9 @@ class StaticLibrary(BuildTarget):
class SharedLibrary(BuildTarget):
known_kwargs = known_shlib_kwargs
def __init__(self, name, subdir, subproject, for_machine: MachineChoice, sources, objects, environment, kwargs):
def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice,
sources: T.List[File], structured_sources: T.Optional['StructuredSources'],
objects, environment: environment.Environment, kwargs):
self.typename = 'shared library'
self.soversion = None
self.ltversion = None
@ -1975,7 +2062,7 @@ class SharedLibrary(BuildTarget):
self.debug_filename = None
# Use by the pkgconfig module
self.shared_library_only = False
super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs)
super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, environment, kwargs)
if 'rust' in self.compilers:
# If no crate type is specified, or it's the generic lib type, use dylib
if not hasattr(self, 'rust_crate_type') or self.rust_crate_type == 'lib':
@ -2285,12 +2372,15 @@ class SharedLibrary(BuildTarget):
class SharedModule(SharedLibrary):
known_kwargs = known_shmod_kwargs
def __init__(self, name, subdir, subproject, for_machine: MachineChoice, sources, objects, environment, kwargs):
def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice,
sources: T.List[File], structured_sources: T.Optional['StructuredSources'],
objects, environment: environment.Environment, kwargs):
if 'version' in kwargs:
raise MesonException('Shared modules must not specify the version kwarg.')
if 'soversion' in kwargs:
raise MesonException('Shared modules must not specify the soversion kwarg.')
super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs)
super().__init__(name, subdir, subproject, for_machine, sources,
structured_sources, objects, environment, kwargs)
self.typename = 'shared module'
# We need to set the soname in cases where build files link the module
# to build targets, see: https://github.com/mesonbuild/meson/issues/9492
@ -2610,15 +2700,19 @@ class AliasTarget(RunTarget):
class Jar(BuildTarget):
known_kwargs = known_jar_kwargs
def __init__(self, name, subdir, subproject, for_machine: MachineChoice, sources, objects, environment, kwargs):
def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice,
sources: T.List[File], structured_sources: T.Optional['StructuredSources'],
objects, environment: environment.Environment, kwargs):
self.typename = 'jar'
super().__init__(name, subdir, subproject, for_machine, sources, objects, environment, kwargs)
super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, environment, kwargs)
for s in self.sources:
if not s.endswith('.java'):
raise InvalidArguments(f'Jar source {s} is not a java file.')
for t in self.link_targets:
if not isinstance(t, Jar):
raise InvalidArguments(f'Link target {t} is not a jar target.')
if self.structured_sources:
raise InvalidArguments(f'structured sources are not supported in Java targets.')
self.filename = self.name + '.jar'
self.outputs = [self.filename]
self.java_args = kwargs.get('java_args', [])

@ -14,6 +14,8 @@
# This file contains the detection logic for external dependencies.
# Custom logic for several other packages are in separate files.
from __future__ import annotations
import copy
import os
import collections
@ -29,6 +31,7 @@ from ..mesonlib import version_compare_many
if T.TYPE_CHECKING:
from .._typing import ImmutableListProtocol
from ..build import StructuredSources
from ..compilers.compilers import Compiler
from ..environment import Environment
from ..interpreterbase import FeatureCheckBase
@ -91,7 +94,7 @@ class Dependency(HoldableObject):
# Raw -L and -l arguments without manual library searching
# If None, self.link_args will be used
self.raw_link_args: T.Optional[T.List[str]] = None
self.sources: T.List[T.Union['FileOrString', 'CustomTarget']] = []
self.sources: T.List[T.Union['FileOrString', 'CustomTarget', 'StructuredSources']] = []
self.include_type = self._process_include_type_kw(kwargs)
self.ext_deps: T.List[Dependency] = []
self.d_features: T.DefaultDict[str, T.List[T.Any]] = collections.defaultdict(list)
@ -148,7 +151,7 @@ class Dependency(HoldableObject):
def found(self) -> bool:
return self.is_found
def get_sources(self) -> T.List[T.Union['FileOrString', 'CustomTarget']]:
def get_sources(self) -> T.List[T.Union['FileOrString', 'CustomTarget', 'StructuredSources']]:
"""Source files that need to be added to the target.
As an example, gtest-all.cc when using GTest."""
return self.sources
@ -228,7 +231,7 @@ class InternalDependency(Dependency):
link_args: T.List[str],
libraries: T.List[T.Union['BuildTarget', 'CustomTarget']],
whole_libraries: T.List[T.Union['BuildTarget', 'CustomTarget']],
sources: T.Sequence[T.Union['FileOrString', 'CustomTarget']],
sources: T.Sequence[T.Union['FileOrString', 'CustomTarget', StructuredSources]],
ext_deps: T.List[Dependency], variables: T.Dict[str, T.Any],
d_module_versions: T.List[str], d_import_dirs: T.List['IncludeDirs']):
super().__init__(DependencyTypeName('internal'), {})

@ -106,7 +106,7 @@ if T.TYPE_CHECKING:
# Input source types passed to the build.Target classes
SourceOutputs = T.Union[mesonlib.File, build.GeneratedList,
build.BuildTarget, build.CustomTargetIndex, build.CustomTarget,
build.ExtractedObjects, build.GeneratedList]
build.ExtractedObjects, build.GeneratedList, build.StructuredSources]
def _project_version_validator(value: T.Union[T.List, str, mesonlib.File, None]) -> T.Optional[str]:
@ -386,6 +386,7 @@ class Interpreter(InterpreterBase, HoldableObject):
'run_command': self.func_run_command,
'run_target': self.func_run_target,
'set_variable': self.func_set_variable,
'structured_sources': self.func_structured_sources,
'subdir': self.func_subdir,
'shared_library': self.func_shared_lib,
'shared_module': self.func_shared_module,
@ -439,6 +440,7 @@ class Interpreter(InterpreterBase, HoldableObject):
build.InstallDir: OBJ.InstallDirHolder,
build.IncludeDirs: OBJ.IncludeDirsHolder,
build.EnvironmentVariables: OBJ.EnvironmentVariablesHolder,
build.StructuredSources: OBJ.StructuredSourcesHolder,
compilers.RunResult: compilerOBJ.TryRunResultHolder,
dependencies.ExternalLibrary: OBJ.ExternalLibraryHolder,
coredata.UserFeatureOption: OBJ.FeatureOptionHolder,
@ -2110,6 +2112,31 @@ external dependencies (including libraries) must go to "dependencies".''')
self.build.symlinks.append(l)
return l
@FeatureNew('structured_sources', '0.62.0')
@typed_pos_args('structured_sources', object, optargs=[dict])
@noKwargs
@noArgsFlattening
def func_structured_sources(
self, node: mparser.BaseNode,
args: T.Tuple[object, T.Optional[T.Dict[str, object]]],
kwargs: 'TYPE_kwargs') -> build.StructuredSources:
valid_types = (str, mesonlib.File, build.GeneratedList, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)
sources: T.Dict[str, T.List[T.Union['mesonlib.FileOrString', 'build.GeneratedTypes']]] = collections.defaultdict(list)
for arg in mesonlib.listify(args[0]):
if not isinstance(arg, valid_types):
raise InvalidArguments(f'structured_sources: type "{type(arg)}" is not valid')
sources[''].append(arg)
if args[1]:
if '' in args[1]:
raise InvalidArguments('structured_sources: keys to dictionary argument may not be an empty string.')
for k, v in args[1].items():
for arg in mesonlib.listify(v):
if not isinstance(arg, valid_types):
raise InvalidArguments(f'structured_sources: type "{type(arg)}" is not valid')
sources[k].append(arg)
return build.StructuredSources(sources)
@typed_pos_args('subdir', str)
@typed_kwargs(
'subdir',
@ -2757,7 +2784,7 @@ Try setting b_lundef to false instead.'''.format(self.coredata.options[OptionKey
results.append(s)
elif isinstance(s, (build.GeneratedList, build.BuildTarget,
build.CustomTargetIndex, build.CustomTarget,
build.ExtractedObjects)):
build.ExtractedObjects, build.StructuredSources)):
results.append(s)
else:
raise InterpreterException(f'Source item is {s!r} instead of '
@ -2840,7 +2867,7 @@ Try setting b_lundef to false instead.'''.format(self.coredata.options[OptionKey
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, kwargs, targetclass):
@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'])
@ -2873,8 +2900,39 @@ Try setting b_lundef to false instead.'''.format(self.coredata.options[OptionKey
# passed to library() when default_library == 'static'.
kwargs = {k: v for k, v in kwargs.items() if k in targetclass.known_kwargs}
srcs: T.List['SourceInputs'] = []
struct: T.Optional[build.StructuredSources] = build.StructuredSources()
for s in sources:
if isinstance(s, build.StructuredSources):
struct = struct + s
else:
srcs.append(s)
if not struct:
struct = None
else:
# Validate that we won't end up with two outputs with the same name.
# i.e, don't allow:
# [structured_sources('foo/bar.rs'), structured_sources('bar/bar.rs')]
for v in struct.sources.values():
outputs: T.Set[str] = set()
for f in v:
o: T.List[str]
if isinstance(f, str):
o = [os.path.basename(f)]
elif isinstance(f, mesonlib.File):
o = [f.fname]
else:
o = f.get_outputs()
conflicts = outputs.intersection(o)
if conflicts:
raise InvalidArguments.from_node(
f"Conflicting sources in structured sources: {', '.join(sorted(conflicts))}",
node=node)
outputs.update(o)
kwargs['include_directories'] = self.extract_incdirs(kwargs)
target = targetclass(name, self.subdir, self.subproject, for_machine, sources, objs, self.environment, kwargs)
target = targetclass(name, self.subdir, self.subproject, for_machine, srcs, struct, objs, self.environment, kwargs)
target.project_version = self.project_version
self.add_stdlib_info(target)

@ -978,3 +978,9 @@ class GeneratorHolder(ObjectHolder[build.Generator]):
preserve_path_from, extra_args=kwargs['extra_args'])
return gl
class StructuredSourcesHolder(ObjectHolder[build.StructuredSources]):
def __init__(self, sources: build.StructuredSources, interp: 'Interpreter'):
super().__init__(sources, interp)

@ -41,6 +41,7 @@ if T.TYPE_CHECKING:
from ..build import ConfigurationData
from ..coredata import KeyedOptionDictType, UserOption
from ..compilers.compilers import Compiler
from ..mparser import BaseNode
FileOrString = T.Union['File', str]
@ -171,6 +172,15 @@ class MesonException(Exception):
self.lineno = lineno
self.colno = colno
@classmethod
def from_node(cls, *args: object, node: BaseNode) -> MesonException:
"""Create a MesonException with location data from a BaseNode
:param node: A BaseNode to set location data from
:return: A Meson Exception instance
"""
return cls(*args, file=node.filename, lineno=node.lineno, colno=node.colno)
class MesonBugException(MesonException):
'''Exceptions thrown when there is a clear Meson bug that should be reported'''

@ -607,10 +607,10 @@ class GnomeModule(ExtensionModule):
def _get_link_args(self, state: 'ModuleState',
lib: T.Union[build.SharedLibrary, build.StaticLibrary],
depends: T.Sequence[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString']],
depends: T.Sequence[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]],
include_rpath: bool = False,
use_gir_args: bool = False
) -> T.Tuple[T.List[str], T.List[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString']]]:
) -> T.Tuple[T.List[str], T.List[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]]]:
link_command: T.List[str] = []
new_depends = list(depends)
# Construct link args
@ -638,12 +638,12 @@ class GnomeModule(ExtensionModule):
def _get_dependencies_flags(
self, deps: T.Sequence[T.Union['Dependency', build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]],
state: 'ModuleState',
depends: T.Sequence[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString']],
depends: T.Sequence[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]],
include_rpath: bool = False,
use_gir_args: bool = False,
separate_nodedup: bool = False
) -> T.Tuple[OrderedSet[str], OrderedSet[str], OrderedSet[str], T.Optional[T.List[str]], OrderedSet[str],
T.List[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString']]]:
T.List[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]]]:
cflags: OrderedSet[str] = OrderedSet()
internal_ldflags: OrderedSet[str] = OrderedSet()
external_ldflags: OrderedSet[str] = OrderedSet()
@ -931,7 +931,7 @@ class GnomeModule(ExtensionModule):
girfile: str,
scan_command: T.Sequence[T.Union['FileOrString', Executable, ExternalProgram, OverrideProgram]],
generated_files: T.Sequence[T.Union[str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]],
depends: T.Sequence[T.Union['FileOrString', build.BuildTarget, 'build.GeneratedTypes']],
depends: T.Sequence[T.Union['FileOrString', build.BuildTarget, 'build.GeneratedTypes', build.StructuredSources]],
kwargs: T.Dict[str, T.Any]) -> GirTarget:
install = kwargs['install_gir']
if install is None:
@ -989,8 +989,8 @@ class GnomeModule(ExtensionModule):
def _gather_typelib_includes_and_update_depends(
state: 'ModuleState',
deps: T.Sequence[T.Union[Dependency, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]],
depends: T.Sequence[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString']]
) -> T.Tuple[T.List[str], T.List[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString']]]:
depends: T.Sequence[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]]
) -> T.Tuple[T.List[str], T.List[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]]]:
# Need to recursively add deps on GirTarget sources from our
# dependencies and also find the include directories needed for the
# typelib generation custom target below.
@ -1092,7 +1092,7 @@ class GnomeModule(ExtensionModule):
srcdir = os.path.join(state.environment.get_source_dir(), state.subdir)
builddir = os.path.join(state.environment.get_build_dir(), state.subdir)
depends: T.List[T.Union['FileOrString', 'build.GeneratedTypes', build.BuildTarget]] = []
depends: T.List[T.Union['FileOrString', 'build.GeneratedTypes', build.BuildTarget, build.StructuredSources]] = []
depends.extend(gir_dep.sources)
depends.extend(girtargets)

@ -17,7 +17,7 @@ import typing as T
from . import ExtensionModule, ModuleReturnValue
from .. import mlog
from ..build import BothLibraries, BuildTarget, CustomTargetIndex, Executable, ExtractedObjects, GeneratedList, IncludeDirs, CustomTarget
from ..build import BothLibraries, BuildTarget, CustomTargetIndex, Executable, ExtractedObjects, GeneratedList, IncludeDirs, CustomTarget, StructuredSources
from ..dependencies import Dependency, ExternalLibrary
from ..interpreter.interpreter import TEST_KWARGS
from ..interpreterbase import ContainerTypeInfo, InterpreterException, KwargInfo, FeatureNew, typed_kwargs, typed_pos_args, noPosargs
@ -149,10 +149,9 @@ class RustModule(ExtensionModule):
new_target_kwargs['dependencies'] = new_target_kwargs.get('dependencies', []) + dependencies
new_target = Executable(
name, base_target.subdir, state.subproject,
base_target.for_machine, base_target.sources,
base_target.objects, base_target.environment,
new_target_kwargs
name, base_target.subdir, state.subproject, base_target.for_machine,
base_target.sources, base_target.structured_sources,
base_target.objects, base_target.environment, new_target_kwargs
)
test = self.interpreter.make_test(
@ -203,7 +202,7 @@ class RustModule(ExtensionModule):
name: str
if isinstance(header, File):
name = header.fname
elif isinstance(header, (BuildTarget, BothLibraries, ExtractedObjects)):
elif isinstance(header, (BuildTarget, BothLibraries, ExtractedObjects, StructuredSources)):
raise InterpreterException('bindgen source file must be a C header, not an object or build target')
else:
name = header.get_outputs()[0]

@ -0,0 +1,18 @@
# SPDX-License-Identifer: Apache-2.0
# Copyright © 2021 Intel Corporation
"""Helper script to copy files at build time.
This is easier than trying to detect whether to use copy, cp, or something else.
"""
import shutil
import typing as T
def run(args: T.List[str]) -> int:
try:
shutil.copy2(args[0], args[1])
except Exception:
return 1
return 0

@ -0,0 +1,13 @@
project('structured_source with empty string key')
if not add_languages(['rust'], required : false, native : false)
error('MESON_SKIP_TEST: Rust is required but not found.')
endif
executable(
'main',
structured_sources(
'main.rs',
{'' : 'main.rs'},
)
)

@ -0,0 +1,7 @@
{
"stdout": [
{
"line": "test cases/failing/121 structured source empty string/meson.build:7:0: ERROR: structured_sources: keys to dictionary argument may not be an empty string."
}
]
}

@ -0,0 +1,17 @@
project('structured_source with empty string key')
if not add_languages(['rust'], required : false, native : false)
error('MESON_SKIP_TEST: Rust is required but not found.')
endif
executable(
'main',
[
structured_sources(
'main.rs',
),
structured_sources(
'main.rs',
),
],
)

@ -0,0 +1,7 @@
{
"stdout": [
{
"line": "test cases/failing/122 structured_sources conflicts/meson.build:7:0: ERROR: Conflicting sources in structured sources: main.rs"
}
]
}

@ -0,0 +1,20 @@
#!/usr/bin/env python3
import argparse
import textwrap
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument('output')
args = parser.parse_args()
with open(args.output, 'w') as f:
f.write(textwrap.dedent('''\
pub fn bar() -> () {
println!("Hello, World!");
}'''))
if __name__ == "__main__":
main()

@ -0,0 +1,39 @@
project('structured input', 'rust')
foo_mod_rs = configure_file(
input : 'src/foo.rs.in',
output : 'mod.rs',
configuration : {'message' : 'Hello, World!'},
)
conf_file = executable(
'main_conf_file',
structured_sources(
'src/main.rs',
{'foo' : [foo_mod_rs]},
),
)
ct = custom_target(
'foo.rs',
output : 'foo.rs',
command : ['gen.py', '@OUTPUT@'],
)
target = executable(
'main_custom_target',
structured_sources(
['src/main.rs', ct],
),
)
# Should not be coppied
executable(
'no_copy_target',
structured_sources(
['src2/main-unique.rs'],
{'foo': 'src2/foo/mod.rs'},
),
)
test('no-copy', find_program('no_copy_test.py'), args : meson.current_build_dir())

@ -0,0 +1,18 @@
#!/usr/bin/env python3
import argparse
import os
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument('builddir')
args = parser.parse_args()
for _, _, files in os.walk(args.builddir):
if 'main-unique.rs' in files:
exit(1)
if __name__ == "__main__":
main()

@ -0,0 +1,4 @@
pub fn bar() -> () {
println!("@message@");
}

@ -0,0 +1,5 @@
mod foo;
fn main() {
foo::bar();
}

@ -0,0 +1,4 @@
pub fn bar() -> () {
println!("Hello, World!");
}

@ -0,0 +1,5 @@
mod foo;
fn main() {
foo::bar();
}

@ -4000,6 +4000,7 @@ class AllPlatformTests(BasePlatformTests):
def output_name(name, type_):
return type_(name=name, subdir=None, subproject=None,
for_machine=MachineChoice.HOST, sources=[],
structured_sources=None,
objects=[], environment=env, kwargs={}).filename
shared_lib_name = lambda name: output_name(name, SharedLibrary)

Loading…
Cancel
Save