Merge pull request #4547 from mensinda/introIncDirs

mintro: Save introspection to disk and --targets modifications
pull/4734/head
Jussi Pakkanen 6 years ago committed by GitHub
commit a34ac74cf9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 150
      docs/markdown/IDE-integration.md
  2. 22
      docs/markdown/snippets/introspect_multiple.md
  3. 69
      mesonbuild/backend/backends.py
  4. 110
      mesonbuild/backend/ninjabackend.py
  5. 21
      mesonbuild/build.py
  6. 2
      mesonbuild/compilers/__init__.py
  7. 9
      mesonbuild/compilers/c.py
  8. 31
      mesonbuild/compilers/compilers.py
  9. 9
      mesonbuild/compilers/cs.py
  10. 20
      mesonbuild/compilers/d.py
  11. 7
      mesonbuild/compilers/fortran.py
  12. 9
      mesonbuild/compilers/java.py
  13. 11
      mesonbuild/compilers/rust.py
  14. 7
      mesonbuild/compilers/swift.py
  15. 13
      mesonbuild/compilers/vala.py
  16. 3
      mesonbuild/environment.py
  17. 2
      mesonbuild/mconf.py
  18. 280
      mesonbuild/mintro.py
  19. 8
      mesonbuild/msetup.py
  20. 228
      run_unittests.py
  21. 14
      test cases/unit/49 introspection/meson.build
  22. 2
      test cases/unit/49 introspection/sharedlib/meson.build
  23. 9
      test cases/unit/49 introspection/sharedlib/shared.cpp
  24. 10
      test cases/unit/49 introspection/sharedlib/shared.hpp
  25. 2
      test cases/unit/49 introspection/staticlib/meson.build
  26. 5
      test cases/unit/49 introspection/staticlib/static.c
  27. 3
      test cases/unit/49 introspection/staticlib/static.h
  28. 13
      test cases/unit/49 introspection/t1.cpp
  29. 8
      test cases/unit/49 introspection/t2.cpp
  30. 16
      test cases/unit/49 introspection/t3.cpp

@ -4,39 +4,110 @@ short-description: Meson's API to integrate Meson support into an IDE
# IDE integration
Meson has exporters for Visual Studio and XCode, but writing a custom backend for every IDE out there is not a scalable approach. To solve this problem, Meson provides an API that makes it easy for any IDE or build tool to integrate Meson builds and provide an experience comparable to a solution native to the IDE.
Meson has exporters for Visual Studio and XCode, but writing a custom backend
for every IDE out there is not a scalable approach. To solve this problem,
Meson provides an API that makes it easy for any IDE or build tools to
integrate Meson builds and provide an experience comparable to a solution
native to the IDE.
The basic tool for this is `meson introspect`.
All the resources required for such a IDE integration can be found in
the `meson-info` directory in the build directory.
The first thing to do when setting up a Meson project in an IDE is to select the source and build directories. For this example we assume that the source resides in an Eclipse-like directory called `workspace/project` and the build tree is nested inside it as `workspace/project/build`. First we initialise Meson by running the following command in the source directory.
The first thing to do when setting up a Meson project in an IDE is to select
the source and build directories. For this example we assume that the source
resides in an Eclipse-like directory called `workspace/project` and the build
tree is nested inside it as `workspace/project/build`. First, we initialize
Meson by running the following command in the source directory.
meson builddir
For the remainder of the document we assume that all commands are executed inside the build directory unless otherwise specified.
With this command meson will configure the project and also generate
introspection information that is stored in `intro-*.json` files in the
`meson-info` directory. The introspection dump will be automatically updated
when meson is (re)configured, or the build options change. Thus, an IDE can
watch for changes in this directory to know when something changed.
The first thing you probably want is to get a list of top level targets. For that we use the introspection tool. It comes with extensive command line help so we recommend using that in case problems appear.
The `meson-info` directory should contain the following files:
meson introspect --targets
File | Description
------------------------------- | ---------------------------------------------------------------------
`intro-benchmarks.json` | Lists all benchmarks
`intro-buildoptions.json` | Contains a full list of meson configuration options for the project
`intro-buildsystem_files.json` | Full list of all meson build files
`intro-dependencies.json` | Lists all dependencies used in the project
`intro-installed.json` | Contains mapping of files to their installed location
`intro-projectinfo.json` | Stores basic information about the project (name, version, etc.)
`intro-targets.json` | Full list of all build targets
`intro-tests.json` | Lists all tests with instructions how to run them
The JSON formats will not be specified in this document. The easiest way of learning them is to look at sample output from the tool.
The content of the JSON files is further specified in the remainder of this document.
Once you have a list of targets, you probably need the list of source files that comprise the target. To get this list for a target, say `exampletarget`, issue the following command.
## The `targets` section
meson introspect --target-files exampletarget
The most important file for an IDE is probably `intro-targets.json`. Here each
target with its sources and compiler parameters is specified. The JSON format
for one target is defined as follows:
In order to make code completion work, you need the compiler flags for each compilation step. Meson does not provide this itself, but the Ninja tool Meson uses to build does provide it. To find out the compile steps necessary to build target foo, issue the following command.
```json
{
"name": "Name of the target",
"id": "The internal ID meson uses",
"type": "<TYPE>",
"filename": ["list", "of", "generated", "files"],
"build_by_default": true / false,
"target_sources": [],
"installed": true / false,
}
```
ninja -t commands foo
If the key `installed` is set to `true`, the key `install_filename` will also
be present. It stores the installation location for each file in `filename`.
If one file in `filename` is not installed, its corresponding install location
is set to `null`.
Note that if the target has dependencies (such as generated sources), then the commands for those show up in this list as well, so you need to do some filtering. Alternatively you can grab every command invocation in the [Clang tools db](https://clang.llvm.org/docs/JSONCompilationDatabase.html) format that is written to a file called `compile_commands.json` in the build directory.
A target usually generates only one file. However, it is possible for custom
targets to have multiple outputs.
## Build Options
### Target sources
The `intro-targets.json` file also stores a list of all source objects of the
target in the `target_sources`. With this information, an IDE can provide code
completion for all source files.
```json
{
"language": "language ID",
"compiler": ["The", "compiler", "command"],
"parameters": ["list", "of", "compiler", "parameters"],
"sources": ["list", "of", "all", "source", "files", "for", "this", "language"],
"generated_sources": ["list", "of", "all", "source", "files", "that", "where", "generated", "somewhere", "else"]
}
```
The next thing to display is the list of options that can be set. These include build type and so on. Here's how to extract them.
It should be noted that the compiler parameters stored in the `parameters`
differ from the actual parameters used to compile the file. This is because
the parameters are optimized for the usage in an IDE to provide autocompletion
support, etc. It is thus not recommended to use this introspection information
for actual compilation.
meson introspect --buildoptions
### Possible values for `type`
This command returns a list of all supported buildoptions with the format:
The following table shows all valid types for a target.
value of `type` | Description
---------------- | -------------------------------------------------------------------------------------------------
`executable` | This target will generate an executable file
`static library` | Target for a static library
`shared library` | Target for a shared library
`shared module` | A shared library that is meant to be used with dlopen rather than linking into something else
`custom` | A custom target
`run` | A Meson run target
`jar` | A Java JAR target
## Build Options
The list of all build options (build type, warning level, etc.) is stored in
the `intro-buildoptions.json` file. Here is the JSON format for each option.
```json
{
@ -56,7 +127,8 @@ The supported types are:
- integer
- array
For the type `combo` the key `choices` is also present. Here all valid values for the option are stored.
For the type `combo` the key `choices` is also present. Here all valid values
for the option are stored.
The possible values for `section` are:
@ -74,25 +146,49 @@ Since Meson 0.50.0 it is also possible to get the default buildoptions
without a build directory by providing the root `meson.build` instead of a
build directory to `meson introspect --buildoptions`.
Running `--buildoptions` without a build directory produces the same output as running
it with a freshly configured build directory.
Running `--buildoptions` without a build directory produces the same output as
running it with a freshly configured build directory.
However, this behavior is not guaranteed if subprojects are present. Due to internal
limitations all subprojects are processed even if they are never used in a real meson run.
Because of this options for the subprojects can differ.
However, this behavior is not guaranteed if subprojects are present. Due to
internal limitations all subprojects are processed even if they are never used
in a real meson run. Because of this options for the subprojects can differ.
## Tests
Compilation and unit tests are done as usual by running the `ninja` and `ninja test` commands. A JSON formatted result log can be found in `workspace/project/builddir/meson-logs/testlog.json`.
Compilation and unit tests are done as usual by running the `ninja` and
`ninja test` commands. A JSON formatted result log can be found in
`workspace/project/builddir/meson-logs/testlog.json`.
When these tests fail, the user probably wants to run the failing test in a
debugger. To make this as integrated as possible, extract the tests from the
`intro-tests.json` and `intro-benchmarks.json` files. This provides you with
all the information needed to run the test: what command to execute, command
line arguments and environment variable settings.
```json
{
"name": "name of the test",
"workdir": "the working directory (can be null)",
"timeout": "the test timeout",
"suite": ["list", "of", "test", "suites"],
"is_parallel": true / false,
"cmd": ["command", "to", "run"],
"env": {
"VARIABLE1": "value 1",
"VARIABLE2": "value 2"
}
}
```
When these tests fail, the user probably wants to run the failing test in a debugger. To make this as integrated as possible, extract the test test setups with this command.
# Programmatic interface
meson introspect --tests
Meson also provides the `meson introspect` for project introspection via the
command line. Use `meson introspect -h` to see all available options.
This provides you with all the information needed to run the test: what command to execute, command line arguments and environment variable settings.
This API can also work without a build directory for the `--projectinfo` command.
# Existing integrations
- [Gnome Builder](https://wiki.gnome.org/Apps/Builder)
- [Eclipse CDT](https://www.eclipse.org/cdt/) (experimental)
- [Meson Cmake Wrapper](https://github.com/prozum/meson-cmake-wrapper) (for cmake IDEs)
- [Meson Cmake Wrapper](https://github.com/prozum/meson-cmake-wrapper) (for cmake IDEs)

@ -0,0 +1,22 @@
## Added option to introspect multiple parameters at once
Meson introspect can now print the results of multiple introspection
commands in a single call. The results are then printed as a single JSON
object.
The format for a single command was not changed to keep backward
compatibility.
Furthermore the option `-a,--all`, `-i,--indent` and `-f,--force-object-output`
were added to print all introspection information in one go, format the
JSON output (the default is still compact JSON) and force use the new
output format, even if only one introspection command was given.
A complete introspection dump is also stored in the `meson-info`
directory. This dump will be (re)generated each time meson updates the
configuration of the build directory.
Additionlly the format of `meson introspect target` was changed:
- New: the `sources` key. It stores the source files of a target and their compiler parameters.
- Added new target types (`jar`, `shared module`).

@ -156,6 +156,8 @@ class Backend:
self.build = build
self.environment = build.environment
self.processed_targets = {}
self.build_dir = self.environment.get_build_dir()
self.source_dir = self.environment.get_source_dir()
self.build_to_src = mesonlib.relpath(self.environment.get_source_dir(),
self.environment.get_build_dir())
@ -683,7 +685,7 @@ class Backend:
def write_test_file(self, datafile):
self.write_test_serialisation(self.build.get_tests(), datafile)
def write_test_serialisation(self, tests, datafile):
def create_test_serialisation(self, tests):
arr = []
for t in tests:
exe = t.get_exe()
@ -730,7 +732,10 @@ class Backend:
exe_wrapper, t.is_parallel, cmd_args, t.env,
t.should_fail, t.timeout, t.workdir, extra_paths)
arr.append(ts)
pickle.dump(arr, datafile)
return arr
def write_test_serialisation(self, tests, datafile):
pickle.dump(self.create_test_serialisation(tests), datafile)
def generate_depmf_install(self, d):
if self.build.dep_manifest_name is None:
@ -974,9 +979,7 @@ class Backend:
cmd = s['exe'] + s['args']
subprocess.check_call(cmd, env=child_env)
def create_install_data_files(self):
install_data_file = os.path.join(self.environment.get_scratch_dir(), 'install.dat')
def create_install_data(self):
strip_bin = self.environment.binaries.host.lookup_entry('strip')
if strip_bin is None:
if self.environment.is_cross_build():
@ -997,8 +1000,12 @@ class Backend:
self.generate_data_install(d)
self.generate_custom_install_script(d)
self.generate_subdir_install(d)
return d
def create_install_data_files(self):
install_data_file = os.path.join(self.environment.get_scratch_dir(), 'install.dat')
with open(install_data_file, 'wb') as ofile:
pickle.dump(d, ofile)
pickle.dump(self.create_install_data(), ofile)
def generate_target_install(self, d):
for t in self.build.get_targets().values():
@ -1144,3 +1151,53 @@ class Backend:
dst_dir = os.path.join(dst_dir, os.path.basename(src_dir))
d.install_subdirs.append([src_dir, dst_dir, sd.install_mode,
sd.exclude])
def get_introspection_data(self, target_id, target):
'''
Returns a list of source dicts with the following format for a given target:
[
{
"language": "<LANG>",
"compiler": ["result", "of", "comp.get_exelist()"],
"parameters": ["list", "of", "compiler", "parameters],
"sources": ["list", "of", "all", "<LANG>", "source", "files"],
"generated_sources": ["list", "of", "generated", "source", "files"]
}
]
This is a limited fallback / reference implementation. The backend should override this method.
'''
if isinstance(target, (build.CustomTarget, build.BuildTarget)):
source_list_raw = target.sources + target.extra_files
source_list = []
for j in source_list_raw:
if isinstance(j, mesonlib.File):
source_list += [j.absolute_path(self.source_dir, self.build_dir)]
elif isinstance(j, str):
source_list += [os.path.join(self.source_dir, j)]
source_list = list(map(lambda x: os.path.normpath(x), source_list))
compiler = []
if isinstance(target, build.CustomTarget):
tmp_compiler = target.command
if not isinstance(compiler, list):
tmp_compiler = [compiler]
for j in tmp_compiler:
if isinstance(j, mesonlib.File):
compiler += [j.absolute_path(self.source_dir, self.build_dir)]
elif isinstance(j, str):
compiler += [j]
elif isinstance(j, (build.BuildTarget, build.CustomTarget)):
compiler += j.get_outputs()
else:
raise RuntimeError('Type "{}" is not supported in get_introspection_data. This is a bug'.format(type(j).__name__))
return [{
'language': 'unknown',
'compiler': compiler,
'parameters': [],
'sources': source_list,
'generated_sources': []
}]
return []

@ -150,6 +150,7 @@ class NinjaBackend(backends.Backend):
self.ninja_filename = 'build.ninja'
self.fortran_deps = {}
self.all_outputs = {}
self.introspection_data = {}
def create_target_alias(self, to_target, outfile):
# We need to use aliases for targets that might be used as directory
@ -321,6 +322,57 @@ int dummy;
return False
return True
def create_target_source_introspection(self, target: build.Target, comp: compilers.Compiler, parameters, sources, generated_sources):
'''
Adds the source file introspection information for a language of a target
Internal introspection storage formart:
self.introspection_data = {
'<target ID>': {
<id tuple>: {
'language: 'lang',
'compiler': ['comp', 'exe', 'list'],
'parameters': ['UNIQUE', 'parameter', 'list'],
'sources': [],
'generated_sources': [],
}
}
}
'''
id = target.get_id()
lang = comp.get_language()
tgt = self.introspection_data[id]
# Find an existing entry or create a new one
id_hash = (lang, tuple(parameters))
src_block = tgt.get(id_hash, None)
if src_block is None:
# Convert parameters
if isinstance(parameters, CompilerArgs):
parameters = parameters.to_native(copy=True)
parameters = comp.compute_parameters_with_absolute_paths(parameters, self.build_dir)
if target.is_cross:
extra_parameters = comp.get_cross_extra_flags(self.environment, False)
if isinstance(parameters, CompilerArgs):
extra_parameters = extra_parameters.to_native(copy=True)
parameters = extra_parameters + parameters
# The new entry
src_block = {
'language': lang,
'compiler': comp.get_exelist(),
'parameters': parameters,
'sources': [],
'generated_sources': [],
}
tgt[id_hash] = src_block
# Make source files absolute
sources = [x.absolute_path(self.source_dir, self.build_dir) if isinstance(x, File) else os.path.normpath(os.path.join(self.build_dir, x))
for x in sources]
generated_sources = [x.absolute_path(self.source_dir, self.build_dir) if isinstance(x, File) else os.path.normpath(os.path.join(self.build_dir, x))
for x in generated_sources]
# Add the source files
src_block['sources'] += sources
src_block['generated_sources'] += generated_sources
def generate_target(self, target, outfile):
if isinstance(target, build.CustomTarget):
self.generate_custom_target(target, outfile)
@ -330,6 +382,8 @@ int dummy;
if name in self.processed_targets:
return
self.processed_targets[name] = True
# Initialize an empty introspection source list
self.introspection_data[name] = {}
# Generate rules for all dependency targets
self.process_target_dependencies(target, outfile)
# If target uses a language that cannot link to C objects,
@ -770,14 +824,16 @@ int dummy;
# Add possible java generated files to src list
generated_sources = self.get_target_generated_sources(target)
gen_src_list = []
for rel_src, gensrc in generated_sources.items():
dirpart, fnamepart = os.path.split(rel_src)
raw_src = File(True, dirpart, fnamepart)
if rel_src.endswith('.java'):
src_list.append(raw_src)
gen_src_list.append(raw_src)
for src in src_list:
plain_class_path = self.generate_single_java_compile(src, target, compiler, outfile)
compile_args = self.determine_single_java_compile_args(target, compiler)
for src in src_list + gen_src_list:
plain_class_path = self.generate_single_java_compile(src, target, compiler, compile_args, outfile)
class_list.append(plain_class_path)
class_dep_list = [os.path.join(self.get_target_private_dir(target), i) for i in class_list]
manifest_path = os.path.join(self.get_target_private_dir(target), 'META-INF', 'MANIFEST.MF')
@ -803,6 +859,8 @@ int dummy;
elem.add_dep(class_dep_list)
elem.add_item('ARGS', commands)
elem.write(outfile)
# Create introspection information
self.create_target_source_introspection(target, compiler, compile_args, src_list, gen_src_list)
def generate_cs_resource_tasks(self, target, outfile):
args = []
@ -856,10 +914,11 @@ int dummy;
else:
outputs = [outname_rel]
generated_sources = self.get_target_generated_sources(target)
generated_rel_srcs = []
for rel_src in generated_sources.keys():
dirpart, fnamepart = os.path.split(rel_src)
if rel_src.lower().endswith('.cs'):
rel_srcs.append(os.path.normpath(rel_src))
generated_rel_srcs.append(os.path.normpath(rel_src))
deps.append(os.path.normpath(rel_src))
for dep in target.get_external_deps():
@ -867,19 +926,15 @@ int dummy;
commands += self.build.get_project_args(compiler, target.subproject, target.is_cross)
commands += self.build.get_global_args(compiler, target.is_cross)
elem = NinjaBuildElement(self.all_outputs, outputs, 'cs_COMPILER', rel_srcs)
elem = NinjaBuildElement(self.all_outputs, outputs, 'cs_COMPILER', rel_srcs + generated_rel_srcs)
elem.add_dep(deps)
elem.add_item('ARGS', commands)
elem.write(outfile)
self.generate_generator_list_rules(target, outfile)
self.create_target_source_introspection(target, compiler, commands, rel_srcs, generated_rel_srcs)
def generate_single_java_compile(self, src, target, compiler, outfile):
deps = [os.path.join(self.get_target_dir(l), l.get_filename()) for l in target.link_targets]
generated_sources = self.get_target_generated_sources(target)
for rel_src, gensrc in generated_sources.items():
if rel_src.endswith('.java'):
deps.append(rel_src)
def determine_single_java_compile_args(self, target, compiler):
args = []
args += compiler.get_buildtype_args(self.get_option_for_target('buildtype', target))
args += self.build.get_global_args(compiler, target.is_cross)
@ -894,6 +949,14 @@ int dummy;
for idir in i.get_incdirs():
sourcepath += os.path.join(self.build_to_src, i.curdir, idir) + os.pathsep
args += ['-sourcepath', sourcepath]
return args
def generate_single_java_compile(self, src, target, compiler, args, outfile):
deps = [os.path.join(self.get_target_dir(l), l.get_filename()) for l in target.link_targets]
generated_sources = self.get_target_generated_sources(target)
for rel_src, gensrc in generated_sources.items():
if rel_src.endswith('.java'):
deps.append(rel_src)
rel_src = src.rel_to_builddir(self.build_to_src)
plain_class_path = src.fname[:-4] + 'class'
rel_obj = os.path.join(self.get_target_private_dir(target), plain_class_path)
@ -1102,6 +1165,7 @@ int dummy;
element.add_item('ARGS', args)
element.add_dep(extra_dep_files)
element.write(outfile)
self.create_target_source_introspection(target, valac, args, all_files, [])
return other_src[0], other_src[1], vala_c_src
def generate_rust_target(self, target, outfile):
@ -1193,6 +1257,7 @@ int dummy;
element.write(outfile)
if isinstance(target, build.SharedLibrary):
self.generate_shsym(outfile, target)
self.create_target_source_introspection(target, rustc, args, [main_rust_file], [])
def swift_module_file_name(self, target):
return os.path.join(self.get_target_private_dir(target),
@ -1241,12 +1306,14 @@ int dummy;
module_name = self.target_swift_modulename(target)
swiftc = target.compilers['swift']
abssrc = []
relsrc = []
abs_headers = []
header_imports = []
for i in target.get_sources():
if swiftc.can_compile(i):
relsrc = i.rel_to_builddir(self.build_to_src)
abss = os.path.normpath(os.path.join(self.environment.get_build_dir(), relsrc))
rels = i.rel_to_builddir(self.build_to_src)
abss = os.path.normpath(os.path.join(self.environment.get_build_dir(), rels))
relsrc.append(rels)
abssrc.append(abss)
elif self.environment.is_header(i):
relh = i.rel_to_builddir(self.build_to_src)
@ -1330,6 +1397,8 @@ int dummy;
elem.write(outfile)
else:
raise MesonException('Swift supports only executable and static library targets.')
# Introspection information
self.create_target_source_introspection(target, swiftc, compile_args + header_imports + module_includes, relsrc, rel_generated)
def generate_static_link_rules(self, is_cross, outfile):
num_pools = self.environment.coredata.backend_options['backend_max_links'].value
@ -2049,6 +2118,12 @@ rule FORTRAN_DEP_HACK%s
commands = self._generate_single_compile(target, compiler, is_generated)
commands = CompilerArgs(commands.compiler, commands)
# Create introspection information
if is_generated is False:
self.create_target_source_introspection(target, compiler, commands, [src], [])
else:
self.create_target_source_introspection(target, compiler, commands, [], [src])
build_dir = self.environment.get_build_dir()
if isinstance(src, File):
rel_src = src.rel_to_builddir(self.build_to_src)
@ -2663,6 +2738,15 @@ rule FORTRAN_DEP_HACK%s
elem = NinjaBuildElement(self.all_outputs, deps, 'phony', '')
elem.write(outfile)
def get_introspection_data(self, target_id, target):
if target_id not in self.introspection_data or len(self.introspection_data[target_id]) == 0:
return super().get_introspection_data(target_id, target)
result = []
for _, i in self.introspection_data[target_id].items():
result += [i]
return result
def load(build_dir):
filename = os.path.join(build_dir, 'meson-private', 'install.dat')
with open(filename, 'rb') as f:

@ -345,6 +345,8 @@ a hard error in the future.''' % name)
self.install = False
self.build_always_stale = False
self.option_overrides = {}
if not hasattr(self, 'typename'):
raise RuntimeError('Target type is not set for target class "{}". This is a bug'.format(type(self).__name__))
def get_install_dir(self, environment):
# Find the installation directory.
@ -366,6 +368,9 @@ a hard error in the future.''' % name)
def get_subdir(self):
return self.subdir
def get_typename(self):
return self.typename
@staticmethod
def _get_id_hash(target_id):
# We don't really need cryptographic security here.
@ -1361,6 +1366,7 @@ class Executable(BuildTarget):
known_kwargs = known_exe_kwargs
def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
self.typename = 'executable'
if 'pie' not in kwargs and 'b_pie' in environment.coredata.base_options:
kwargs['pie'] = environment.coredata.base_options['b_pie'].value
super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
@ -1450,6 +1456,7 @@ class StaticLibrary(BuildTarget):
known_kwargs = known_stlib_kwargs
def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
self.typename = 'static library'
if 'pic' not in kwargs and 'b_staticpic' in environment.coredata.base_options:
kwargs['pic'] = environment.coredata.base_options['b_staticpic'].value
super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
@ -1509,6 +1516,7 @@ class SharedLibrary(BuildTarget):
known_kwargs = known_shlib_kwargs
def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
self.typename = 'shared library'
self.soversion = None
self.ltversion = None
# Max length 2, first element is compatibility_version, second is current_version
@ -1817,6 +1825,7 @@ class SharedModule(SharedLibrary):
if 'soversion' in kwargs:
raise MesonException('Shared modules must not specify the soversion kwarg.')
super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
self.typename = 'shared module'
def get_default_install_dir(self, environment):
return environment.get_shared_module_dir()
@ -1842,6 +1851,7 @@ class CustomTarget(Target):
])
def __init__(self, name, subdir, subproject, kwargs, absolute_paths=False):
self.typename = 'custom'
super().__init__(name, subdir, subproject, False)
self.dependencies = []
self.extra_depends = []
@ -2083,6 +2093,7 @@ class CustomTarget(Target):
class RunTarget(Target):
def __init__(self, name, command, args, dependencies, subdir, subproject):
self.typename = 'run'
super().__init__(name, subdir, subproject, False)
self.command = command
self.args = args
@ -2110,6 +2121,14 @@ class RunTarget(Target):
def get_filename(self):
return self.name
def get_outputs(self):
if isinstance(self.name, str):
return [self.name]
elif isinstance(self.name, list):
return self.name
else:
raise RuntimeError('RunTarget: self.name is neither a list nor a string. This is a bug')
def type_suffix(self):
return "@run"
@ -2117,6 +2136,7 @@ class Jar(BuildTarget):
known_kwargs = known_jar_kwargs
def __init__(self, name, subdir, subproject, is_cross, sources, objects, environment, kwargs):
self.typename = 'jar'
super().__init__(name, subdir, subproject, is_cross, sources, objects, environment, kwargs)
for s in self.sources:
if not s.endswith('.java'):
@ -2160,6 +2180,7 @@ class CustomTargetIndex:
"""
def __init__(self, target, output):
self.typename = 'custom'
self.target = target
self.output = output

@ -15,6 +15,7 @@
# Public symbols for compilers sub-package when using 'from . import compilers'
__all__ = [
'CompilerType',
'Compiler',
'all_languages',
'base_options',
@ -91,6 +92,7 @@ __all__ = [
# Bring symbols from each module into compilers sub-package namespace
from .compilers import (
CompilerType,
Compiler,
all_languages,
base_options,
clib_langs,

@ -1475,6 +1475,15 @@ class VisualStudioCCompiler(CCompiler):
# msvc does not have a concept of system header dirs.
return ['-I' + path]
def compute_parameters_with_absolute_paths(self, parameter_list, build_dir):
for idx, i in enumerate(parameter_list):
if i[:2] == '-I' or i[:2] == '/I':
parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:]))
elif i[:9] == '/LIBPATH:':
parameter_list[idx] = i[:9] + os.path.normpath(os.path.join(build_dir, i[9:]))
return parameter_list
# Visual Studio is special. It ignores some arguments it does not
# understand and you can't tell it to error out on those.
# http://stackoverflow.com/questions/15259720/how-can-i-make-the-microsoft-c-compiler-treat-unknown-flags-as-errors-rather-t

@ -884,6 +884,9 @@ class Compiler:
def compute_int(self, expression, low, high, guess, prefix, env, extra_args, dependencies):
raise EnvironmentException('%s does not support compute_int ' % self.get_id())
def compute_parameters_with_absolute_paths(self, parameter_list, build_dir):
raise EnvironmentException('%s does not support compute_parameters_with_absolute_paths ' % self.get_id())
def has_members(self, typename, membernames, prefix, env, *, extra_args=None, dependencies=None):
raise EnvironmentException('%s does not support has_member(s) ' % self.get_id())
@ -1547,6 +1550,13 @@ class GnuLikeCompiler(abc.ABC):
return ['-mwindows']
return []
def compute_parameters_with_absolute_paths(self, parameter_list, build_dir):
for idx, i in enumerate(parameter_list):
if i[:2] == '-I' or i[:2] == '-L':
parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:]))
return parameter_list
class GnuCompiler(GnuLikeCompiler):
"""
GnuCompiler represents an actual GCC in its many incarnations.
@ -1776,6 +1786,13 @@ class ArmclangCompiler:
"""
return ['--symdefs=' + implibname]
def compute_parameters_with_absolute_paths(self, parameter_list, build_dir):
for idx, i in enumerate(parameter_list):
if i[:2] == '-I' or i[:2] == '-L':
parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:]))
return parameter_list
# Tested on linux for ICC 14.0.3, 15.0.6, 16.0.4, 17.0.1, 19.0.0
class IntelCompiler(GnuLikeCompiler):
@ -1910,6 +1927,13 @@ class ArmCompiler:
def get_debug_args(self, is_debug):
return clike_debug_args[is_debug]
def compute_parameters_with_absolute_paths(self, parameter_list, build_dir):
for idx, i in enumerate(parameter_list):
if i[:2] == '-I' or i[:2] == '-L':
parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:]))
return parameter_list
class CcrxCompiler:
def __init__(self, compiler_type):
if not self.is_cross:
@ -2003,3 +2027,10 @@ class CcrxCompiler:
continue
result.append(i)
return result
def compute_parameters_with_absolute_paths(self, parameter_list, build_dir):
for idx, i in enumerate(parameter_list):
if i[:9] == '-include=':
parameter_list[idx] = i[:9] + os.path.normpath(os.path.join(build_dir, i[9:]))
return parameter_list

@ -88,6 +88,15 @@ class CsCompiler(Compiler):
def get_pic_args(self):
return []
def compute_parameters_with_absolute_paths(self, parameter_list, build_dir):
for idx, i in enumerate(parameter_list):
if i[:2] == '-L':
parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:]))
if i[:5] == '-lib:':
parameter_list[idx] = i[:5] + os.path.normpath(os.path.join(build_dir, i[5:]))
return parameter_list
def name_string(self):
return ' '.join(self.exelist)

@ -111,6 +111,19 @@ class DCompiler(Compiler):
def get_include_args(self, path, is_system):
return ['-I=' + path]
def compute_parameters_with_absolute_paths(self, parameter_list, build_dir):
for idx, i in enumerate(parameter_list):
if i[:3] == '-I=':
parameter_list[idx] = i[:3] + os.path.normpath(os.path.join(build_dir, i[3:]))
if i[:4] == '-L-L':
parameter_list[idx] = i[:4] + os.path.normpath(os.path.join(build_dir, i[4:]))
if i[:5] == '-L=-L':
parameter_list[idx] = i[:5] + os.path.normpath(os.path.join(build_dir, i[5:]))
if i[:6] == '-Wl,-L':
parameter_list[idx] = i[:6] + os.path.normpath(os.path.join(build_dir, i[6:]))
return parameter_list
def get_warn_args(self, level):
return ['-wi']
@ -511,6 +524,13 @@ class GnuDCompiler(DCompiler):
def get_buildtype_args(self, buildtype):
return d_gdc_buildtype_args[buildtype]
def compute_parameters_with_absolute_paths(self, parameter_list, build_dir):
for idx, i in enumerate(parameter_list):
if i[:2] == '-I' or i[:2] == '-L':
parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:]))
return parameter_list
def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
return self.build_unix_rpath_args(build_dir, from_dir, rpath_paths, build_rpath, install_rpath)

@ -171,6 +171,13 @@ end program prog
def get_module_outdir_args(self, path):
return ['-module', path]
def compute_parameters_with_absolute_paths(self, parameter_list, build_dir):
for idx, i in enumerate(parameter_list):
if i[:2] == '-I' or i[:2] == '-L':
parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:]))
return parameter_list
def module_name_to_filename(self, module_name):
return module_name.lower() + '.mod'

@ -81,6 +81,15 @@ class JavaCompiler(Compiler):
def get_buildtype_args(self, buildtype):
return java_buildtype_args[buildtype]
def compute_parameters_with_absolute_paths(self, parameter_list, build_dir):
for idx, i in enumerate(parameter_list):
if i in ['-cp', '-classpath', '-sourcepath'] and idx + 1 < len(parameter_list):
path_list = parameter_list[idx + 1].split(os.pathsep)
path_list = [os.path.normpath(os.path.join(build_dir, x)) for x in path_list]
parameter_list[idx + 1] = os.pathsep.join(path_list)
return parameter_list
def sanity_check(self, work_dir, environment):
src = 'SanityCheck.java'
obj = 'SanityCheck'

@ -82,3 +82,14 @@ class RustCompiler(Compiler):
def get_optimization_args(self, optimization_level):
return rust_optimization_args[optimization_level]
def compute_parameters_with_absolute_paths(self, parameter_list, build_dir):
for idx, i in enumerate(parameter_list):
if i[:2] == '-L':
for j in ['dependency', 'crate', 'native', 'framework', 'all']:
combined_len = len(j) + 3
if i[:combined_len] == '-L{}='.format(j):
parameter_list[idx] = i[:combined_len] + os.path.normpath(os.path.join(build_dir, i[combined_len:]))
break
return parameter_list

@ -91,6 +91,13 @@ class SwiftCompiler(Compiler):
def get_compile_only_args(self):
return ['-c']
def compute_parameters_with_absolute_paths(self, parameter_list, build_dir):
for idx, i in enumerate(parameter_list):
if i[:2] == '-I' or i[:2] == '-L':
parameter_list[idx] = i[:2] + os.path.normpath(os.path.join(build_dir, i[2:]))
return parameter_list
def sanity_check(self, work_dir, environment):
src = 'swifttest.swift'
source_name = os.path.join(work_dir, src)

@ -66,6 +66,19 @@ class ValaCompiler(Compiler):
return ['--color=' + colortype]
return []
def compute_parameters_with_absolute_paths(self, parameter_list, build_dir):
for idx, i in enumerate(parameter_list):
if i[:9] == '--girdir=':
parameter_list[idx] = i[:9] + os.path.normpath(os.path.join(build_dir, i[9:]))
if i[:10] == '--vapidir=':
parameter_list[idx] = i[:10] + os.path.normpath(os.path.join(build_dir, i[10:]))
if i[:13] == '--includedir=':
parameter_list[idx] = i[:13] + os.path.normpath(os.path.join(build_dir, i[13:]))
if i[:14] == '--metadatadir=':
parameter_list[idx] = i[:14] + os.path.normpath(os.path.join(build_dir, i[14:]))
return parameter_list
def sanity_check(self, work_dir, environment):
code = 'class MesonSanityCheck : Object { }'
args = self.get_cross_extra_flags(environment, link=False)

@ -308,6 +308,7 @@ def search_version(text):
class Environment:
private_dir = 'meson-private'
log_dir = 'meson-logs'
info_dir = 'meson-info'
def __init__(self, source_dir, build_dir, options):
self.source_dir = source_dir
@ -318,8 +319,10 @@ class Environment:
if build_dir is not None:
self.scratch_dir = os.path.join(build_dir, Environment.private_dir)
self.log_dir = os.path.join(build_dir, Environment.log_dir)
self.info_dir = os.path.join(build_dir, Environment.info_dir)
os.makedirs(self.scratch_dir, exist_ok=True)
os.makedirs(self.log_dir, exist_ok=True)
os.makedirs(self.info_dir, exist_ok=True)
try:
self.coredata = coredata.load(self.get_build_dir())
self.first_invocation = False

@ -14,6 +14,7 @@
import os
from . import (coredata, mesonlib, build)
from . import mintro
def add_arguments(parser):
coredata.register_builtin_arguments(parser)
@ -162,6 +163,7 @@ def run(options):
c.print_conf()
if save:
c.save()
mintro.update_build_options(c.coredata, c.build.environment.info_dir)
except ConfException as e:
print('Meson configurator encountered an error:')
raise e

@ -20,7 +20,7 @@ Currently only works for the Ninja backend. Others use generated
project files and don't need this info."""
import json
from . import build, mtest, coredata as cdata
from . import build, coredata as cdata
from . import environment
from . import mesonlib
from . import astinterpreter
@ -29,7 +29,7 @@ from . import mlog
from . import compilers
from . import optinterpreter
from .interpreterbase import InvalidArguments
from .backend import ninjabackend, backends
from .backend import backends
import sys, os
import pathlib
@ -54,26 +54,14 @@ def add_arguments(parser):
help='Information about projects.')
parser.add_argument('--backend', choices=cdata.backendlist, dest='backend', default='ninja',
help='The backend to use for the --buildoptions introspection.')
parser.add_argument('-a', '--all', action='store_true', dest='all', default=False,
help='Print all available information.')
parser.add_argument('-i', '--indent', action='store_true', dest='indent', default=False,
help='Enable pretty printed JSON.')
parser.add_argument('-f', '--force-object-output', action='store_true', dest='force_dict', default=False,
help='Always use the new JSON format for multiple entries (even for 0 and 1 introspection commands)')
parser.add_argument('builddir', nargs='?', default='.', help='The build directory')
def determine_installed_path(target, installdata):
install_targets = []
for i in target.outputs:
for j in installdata.targets:
if os.path.basename(j.fname) == i: # FIXME, might clash due to subprojects.
install_targets += [j]
break
if len(install_targets) == 0:
raise RuntimeError('Something weird happened. File a bug.')
# Normalize the path by using os.path.sep consistently, etc.
# Does not change the effective path.
install_targets = list(map(lambda x: os.path.join(installdata.prefix, x.outdir, os.path.basename(x.fname)), install_targets))
install_targets = list(map(lambda x: str(pathlib.PurePath(x)), install_targets))
return install_targets
def list_installed(installdata):
res = {}
if installdata is not None:
@ -86,54 +74,43 @@ def list_installed(installdata):
res[path] = os.path.join(installdata.prefix, installdir, os.path.basename(path))
for path, installpath, unused_custom_install_mode in installdata.man:
res[path] = os.path.join(installdata.prefix, installpath)
print(json.dumps(res))
return ('installed', res)
def list_targets(coredata, builddata, installdata):
def list_targets(builddata: build.Build, installdata, backend: backends.Backend):
tlist = []
# Fast lookup table for installation files
install_lookuptable = {}
for i in installdata.targets:
outname = os.path.join(installdata.prefix, i.outdir, os.path.basename(i.fname))
install_lookuptable[os.path.basename(i.fname)] = str(pathlib.PurePath(outname))
for (idname, target) in builddata.get_targets().items():
t = {'name': target.get_basename(), 'id': idname}
fname = target.get_filename()
if isinstance(fname, list):
fname = [os.path.join(target.subdir, x) for x in fname]
else:
fname = os.path.join(target.subdir, fname)
t['filename'] = fname
if isinstance(target, build.Executable):
typename = 'executable'
elif isinstance(target, build.SharedLibrary):
typename = 'shared library'
elif isinstance(target, build.StaticLibrary):
typename = 'static library'
elif isinstance(target, build.CustomTarget):
typename = 'custom'
elif isinstance(target, build.RunTarget):
typename = 'run'
else:
typename = 'unknown'
t['type'] = typename
if not isinstance(target, build.Target):
raise RuntimeError('The target object in `builddata.get_targets()` is not of type `build.Target`. Please file a bug with this error message.')
# TODO Change this to the full list in a seperate PR
fname = [os.path.join(target.subdir, x) for x in target.get_outputs()]
if len(fname) == 1:
fname = fname[0]
t = {
'name': target.get_basename(),
'id': idname,
'type': target.get_typename(),
'filename': fname,
'build_by_default': target.build_by_default,
'target_sources': backend.get_introspection_data(idname, target)
}
if installdata and target.should_install():
t['installed'] = True
t['install_filename'] = determine_installed_path(target, installdata)
# TODO Change this to the full list in a seperate PR
t['install_filename'] = [install_lookuptable.get(x, None) for x in target.get_outputs()][0]
else:
t['installed'] = False
t['build_by_default'] = target.build_by_default
tlist.append(t)
print(json.dumps(tlist))
def list_target_files(target_name, coredata, builddata):
try:
t = builddata.targets[target_name]
sources = t.sources + t.extra_files
except KeyError:
print("Unknown target %s." % target_name)
sys.exit(1)
out = []
for i in sources:
if isinstance(i, mesonlib.File):
i = os.path.join(i.subdir, i.fname)
out.append(i)
print(json.dumps(out))
return ('targets', tlist)
class BuildoptionsOptionHelper:
# mimic an argparse namespace
@ -272,7 +249,7 @@ class BuildoptionsInterperter(astinterpreter.AstInterpreter):
self.parse_project()
self.run()
def list_buildoptions_from_source(sourcedir, backend):
def list_buildoptions_from_source(sourcedir, backend, indent):
# Make sure that log entries in other parts of meson don't interfere with the JSON output
mlog.disable()
backend = backends.get_backend_from_name(backend, None)
@ -280,9 +257,31 @@ def list_buildoptions_from_source(sourcedir, backend):
intr.analyze()
# Reenable logging just in case
mlog.enable()
list_buildoptions(intr.coredata)
buildoptions = list_buildoptions(intr.coredata)[1]
print(json.dumps(buildoptions, indent=indent))
def list_target_files(target_name, targets, builddata: build.Build):
result = []
tgt = None
for i in targets:
if i['id'] == target_name:
tgt = i
break
if tgt is None:
print('Target with the ID "{}" could not be found'.format(target_name))
sys.exit(1)
for i in tgt['target_sources']:
result += i['sources'] + i['generated_sources']
def list_buildoptions(coredata):
# TODO Remove this line in a future PR with other breaking changes
result = list(map(lambda x: os.path.relpath(x, builddata.environment.get_source_dir()), result))
return ('target_files', result)
def list_buildoptions(coredata: cdata.CoreData):
optlist = []
dir_option_names = ['bindir',
@ -313,7 +312,7 @@ def list_buildoptions(coredata):
add_keys(optlist, dir_options, 'directory')
add_keys(optlist, coredata.user_options, 'user')
add_keys(optlist, test_options, 'test')
print(json.dumps(optlist))
return ('buildoptions', optlist)
def add_keys(optlist, options, section):
keys = list(options.keys())
@ -347,21 +346,21 @@ def find_buildsystem_files_list(src_dir):
filelist.append(os.path.relpath(os.path.join(root, f), src_dir))
return filelist
def list_buildsystem_files(builddata):
def list_buildsystem_files(builddata: build.Build):
src_dir = builddata.environment.get_source_dir()
filelist = find_buildsystem_files_list(src_dir)
print(json.dumps(filelist))
return ('buildsystem_files', filelist)
def list_deps(coredata):
def list_deps(coredata: cdata.CoreData):
result = []
for d in coredata.deps.values():
if d.found():
result += [{'name': d.name,
'compile_args': d.get_compile_args(),
'link_args': d.get_link_args()}]
print(json.dumps(result))
return ('dependencies', result)
def list_tests(testdata):
def get_test_list(testdata):
result = []
for t in testdata:
to = {}
@ -380,9 +379,15 @@ def list_tests(testdata):
to['suite'] = t.suite
to['is_parallel'] = t.is_parallel
result.append(to)
print(json.dumps(result))
return result
def list_tests(testdata):
return ('tests', get_test_list(testdata))
def list_benchmarks(benchdata):
return ('benchmarks', get_test_list(benchdata))
def list_projinfo(builddata):
def list_projinfo(builddata: build.Build):
result = {'version': builddata.project_version,
'descriptive_name': builddata.project_name}
subprojects = []
@ -392,7 +397,7 @@ def list_projinfo(builddata):
'descriptive_name': builddata.projects.get(k)}
subprojects.append(c)
result['subprojects'] = subprojects
print(json.dumps(result))
return ('projectinfo', result)
class ProjectInfoInterperter(astinterpreter.AstInterpreter):
def __init__(self, source_root, subdir):
@ -418,7 +423,7 @@ class ProjectInfoInterperter(astinterpreter.AstInterpreter):
self.parse_project()
self.run()
def list_projinfo_from_source(sourcedir):
def list_projinfo_from_source(sourcedir, indent):
files = find_buildsystem_files_list(sourcedir)
result = {'buildsystem_files': []}
@ -447,55 +452,112 @@ def list_projinfo_from_source(sourcedir):
subprojects = [obj for name, obj in subprojects.items()]
result['subprojects'] = subprojects
print(json.dumps(result))
print(json.dumps(result, indent=indent))
def run(options):
datadir = 'meson-private'
infodir = 'meson-info'
indent = 4 if options.indent else None
if options.builddir is not None:
datadir = os.path.join(options.builddir, datadir)
infodir = os.path.join(options.builddir, infodir)
if options.builddir.endswith('/meson.build') or options.builddir.endswith('\\meson.build') or options.builddir == 'meson.build':
sourcedir = '.' if options.builddir == 'meson.build' else options.builddir[:-11]
if options.projectinfo:
list_projinfo_from_source(sourcedir)
list_projinfo_from_source(sourcedir, indent)
return 0
if options.buildoptions:
list_buildoptions_from_source(sourcedir, options.backend)
list_buildoptions_from_source(sourcedir, options.backend, indent)
return 0
if not os.path.isdir(datadir):
print('Current directory is not a build dir. Please specify it or '
'change the working directory to it.')
if not os.path.isdir(datadir) or not os.path.isdir(infodir):
print('Current directory is not a meson build directory.'
'Please specify a valid build dir or change the working directory to it.'
'It is also possible that the build directory was generated with an old'
'meson version. Please regenerate it in this case.')
return 1
coredata = cdata.load(options.builddir)
builddata = build.load(options.builddir)
testdata = mtest.load_tests(options.builddir)
benchmarkdata = mtest.load_benchmarks(options.builddir)
# Install data is only available with the Ninja backend
try:
installdata = ninjabackend.load(options.builddir)
except FileNotFoundError:
installdata = None
if options.list_targets:
list_targets(coredata, builddata, installdata)
elif options.list_installed:
list_installed(installdata)
elif options.target_files is not None:
list_target_files(options.target_files, coredata, builddata)
elif options.buildsystem_files:
list_buildsystem_files(builddata)
elif options.buildoptions:
list_buildoptions(coredata)
elif options.tests:
list_tests(testdata)
elif options.benchmarks:
list_tests(benchmarkdata)
elif options.dependencies:
list_deps(coredata)
elif options.projectinfo:
list_projinfo(builddata)
else:
# Load build data to make sure that the version matches
# TODO Find a better solution for this
cdata.load(options.builddir)
results = []
toextract = []
if options.all or options.benchmarks:
toextract += ['benchmarks']
if options.all or options.buildoptions:
toextract += ['buildoptions']
if options.all or options.buildsystem_files:
toextract += ['buildsystem_files']
if options.all or options.dependencies:
toextract += ['dependencies']
if options.all or options.list_installed:
toextract += ['installed']
if options.all or options.projectinfo:
toextract += ['projectinfo']
if options.all or options.list_targets:
toextract += ['targets']
if options.target_files is not None:
targets_file = os.path.join(infodir, 'intro-targets.json')
with open(targets_file, 'r') as fp:
targets = json.load(fp)
builddata = build.load(options.builddir) # TODO remove this in a breaking changes PR
results += [list_target_files(options.target_files, targets, builddata)]
if options.all or options.tests:
toextract += ['tests']
for i in toextract:
curr = os.path.join(infodir, 'intro-{}.json'.format(i))
if not os.path.isfile(curr):
print('Introspection file {} does not exist.'.format(curr))
return 1
with open(curr, 'r') as fp:
results += [(i, json.load(fp))]
if len(results) == 0 and not options.force_dict:
print('No command specified')
return 1
elif len(results) == 1 and not options.force_dict:
# Make to keep the existing output format for a single option
print(json.dumps(results[0][1], indent=indent))
else:
out = {}
for i in results:
out[i[0]] = i[1]
print(json.dumps(out, indent=indent))
return 0
def write_intro_info(intro_info, info_dir):
for i in intro_info:
out_file = os.path.join(info_dir, 'intro-{}.json'.format(i[0]))
tmp_file = os.path.join(info_dir, 'tmp_dump.json')
with open(tmp_file, 'w') as fp:
json.dump(i[1], fp)
fp.flush() # Not sure if this is needed
os.replace(tmp_file, out_file)
def generate_introspection_file(builddata: build.Build, backend: backends.Backend):
coredata = builddata.environment.get_coredata()
benchmarkdata = backend.create_test_serialisation(builddata.get_benchmarks())
testdata = backend.create_test_serialisation(builddata.get_tests())
installdata = backend.create_install_data()
intro_info = [
list_benchmarks(benchmarkdata),
list_buildoptions(coredata),
list_buildsystem_files(builddata),
list_deps(coredata),
list_installed(installdata),
list_projinfo(builddata),
list_targets(builddata, installdata, backend),
list_tests(testdata)
]
write_intro_info(intro_info, builddata.environment.info_dir)
def update_build_options(coredata: cdata.CoreData, info_dir):
intro_info = [
list_buildoptions(coredata)
]
write_intro_info(intro_info, info_dir)

@ -23,6 +23,7 @@ import argparse
from . import environment, interpreter, mesonlib
from . import build
from . import mlog, coredata
from . import mintro
from .mesonlib import MesonException
def add_arguments(parser):
@ -215,6 +216,13 @@ class MesonApp:
coredata.write_cmd_line_file(self.build_dir, self.options)
else:
coredata.update_cmd_line_file(self.build_dir, self.options)
# Generate an IDE introspection file with the same syntax as the already existing API
if self.options.profile:
fname = os.path.join(self.build_dir, 'meson-private', 'profile-introspector.log')
profile.runctx('mintro.generate_introspection_file(b, intr.backend)', globals(), locals(), filename=fname)
else:
mintro.generate_introspection_file(b, intr.backend)
except:
if 'cdf' in locals():
old_cdf = cdf + '.prev'

@ -1221,10 +1221,13 @@ class BasePlatformTests(unittest.TestCase):
self.assertEqual(PurePath(path1), PurePath(path2))
def assertPathListEqual(self, pathlist1, pathlist2):
self.assertEquals(len(pathlist1), len(pathlist2))
self.assertEqual(len(pathlist1), len(pathlist2))
worklist = list(zip(pathlist1, pathlist2))
for i in worklist:
self.assertPathEqual(i[0], i[1])
if i[0] is None:
self.assertEqual(i[0], i[1])
else:
self.assertPathEqual(i[0], i[1])
def assertPathBasenameEqual(self, path, basename):
msg = '{!r} does not end with {!r}'.format(path, basename)
@ -1436,7 +1439,7 @@ class AllPlatformTests(BasePlatformTests):
# Get name of static library
targets = self.introspect('--targets')
self.assertEqual(len(targets), 1)
libname = targets[0]['filename']
libname = targets[0]['filename'] # TODO Change filename back to a list again
# Build and get contents of static library
self.build()
before = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split()
@ -1493,13 +1496,16 @@ class AllPlatformTests(BasePlatformTests):
intro = self.introspect('--targets')
if intro[0]['type'] == 'executable':
intro = intro[::-1]
self.assertPathListEqual(intro[0]['install_filename'], ['/usr/lib/libstat.a'])
self.assertPathListEqual(intro[1]['install_filename'], ['/usr/bin/prog' + exe_suffix])
self.assertPathListEqual([intro[0]['install_filename']], ['/usr/lib/libstat.a'])
self.assertPathListEqual([intro[1]['install_filename']], ['/usr/bin/prog' + exe_suffix])
def test_install_introspection_multiple_outputs(self):
'''
Tests that the Meson introspection API exposes multiple install filenames correctly without crashing
https://github.com/mesonbuild/meson/pull/4555
Reverted to the first file only because of https://github.com/mesonbuild/meson/pull/4547#discussion_r244173438
TODO Change the format to a list officialy in a followup PR
'''
if self.backend is not Backend.ninja:
raise unittest.SkipTest('{!r} backend can\'t install files'.format(self.backend.name))
@ -1508,10 +1514,14 @@ class AllPlatformTests(BasePlatformTests):
intro = self.introspect('--targets')
if intro[0]['type'] == 'executable':
intro = intro[::-1]
self.assertPathListEqual(intro[0]['install_filename'], ['/usr/include/diff.h', '/usr/bin/diff.sh'])
self.assertPathListEqual(intro[1]['install_filename'], ['/opt/same.h', '/opt/same.sh'])
self.assertPathListEqual(intro[2]['install_filename'], ['/usr/include/first.h'])
self.assertPathListEqual(intro[3]['install_filename'], ['/usr/bin/second.sh'])
#self.assertPathListEqual(intro[0]['install_filename'], ['/usr/include/diff.h', '/usr/bin/diff.sh'])
#self.assertPathListEqual(intro[1]['install_filename'], ['/opt/same.h', '/opt/same.sh'])
#self.assertPathListEqual(intro[2]['install_filename'], ['/usr/include/first.h', None])
#self.assertPathListEqual(intro[3]['install_filename'], [None, '/usr/bin/second.sh'])
self.assertPathListEqual([intro[0]['install_filename']], ['/usr/include/diff.h'])
self.assertPathListEqual([intro[1]['install_filename']], ['/opt/same.h'])
self.assertPathListEqual([intro[2]['install_filename']], ['/usr/include/first.h'])
self.assertPathListEqual([intro[3]['install_filename']], [None])
def test_uninstall(self):
exename = os.path.join(self.installdir, 'usr/bin/prog' + exe_suffix)
@ -2559,6 +2569,7 @@ int main(int argc, char **argv) {
for t in t_intro:
id = t['id']
tf_intro = self.introspect(['--target-files', id])
#tf_intro = list(map(lambda x: os.path.relpath(x, testdir), tf_intro)) TODO make paths absolute in future PR
self.assertEqual(tf_intro, expected[id])
self.wipe()
@ -2573,6 +2584,9 @@ int main(int argc, char **argv) {
for t in t_intro:
id = t['id']
tf_intro = self.introspect(['--target-files', id])
print(tf_intro)
#tf_intro = list(map(lambda x: os.path.relpath(x, testdir), tf_intro)) TODO make paths absolute in future PR
print(tf_intro)
self.assertEqual(tf_intro, expected[id])
self.wipe()
@ -3110,6 +3124,198 @@ recommended as it is not supported on some platforms''')
self.maxDiff = None
self.assertListEqual(res_nb, res_wb)
def test_introspect_json_dump(self):
testdir = os.path.join(self.unit_test_dir, '49 introspection')
self.init(testdir)
infodir = os.path.join(self.builddir, 'meson-info')
self.assertPathExists(infodir)
def assertKeyTypes(key_type_list, obj):
for i in key_type_list:
self.assertIn(i[0], obj)
self.assertIsInstance(obj[i[0]], i[1])
root_keylist = [
('benchmarks', list),
('buildoptions', list),
('buildsystem_files', list),
('dependencies', list),
('installed', dict),
('projectinfo', dict),
('targets', list),
('tests', list),
]
test_keylist = [
('cmd', list),
('env', dict),
('name', str),
('timeout', int),
('suite', list),
('is_parallel', bool),
]
buildoptions_keylist = [
('name', str),
('section', str),
('type', str),
('description', str),
]
buildoptions_typelist = [
('combo', str, [('choices', list)]),
('string', str, []),
('boolean', bool, []),
('integer', int, []),
('array', list, []),
]
dependencies_typelist = [
('name', str),
('compile_args', list),
('link_args', list),
]
targets_typelist = [
('name', str),
('id', str),
('type', str),
('filename', str),
('build_by_default', bool),
('target_sources', list),
('installed', bool),
]
targets_sources_typelist = [
('language', str),
('compiler', list),
('parameters', list),
('sources', list),
('generated_sources', list),
]
# First load all files
res = {}
for i in root_keylist:
curr = os.path.join(infodir, 'intro-{}.json'.format(i[0]))
self.assertPathExists(curr)
with open(curr, 'r') as fp:
res[i[0]] = json.load(fp)
assertKeyTypes(root_keylist, res)
# Check Tests and benchmarks
tests_to_find = ['test case 1', 'test case 2', 'benchmark 1']
for i in res['benchmarks'] + res['tests']:
assertKeyTypes(test_keylist, i)
if i['name'] in tests_to_find:
tests_to_find.remove(i['name'])
self.assertListEqual(tests_to_find, [])
# Check buildoptions
buildopts_to_find = {'cpp_std': 'c++11'}
for i in res['buildoptions']:
assertKeyTypes(buildoptions_keylist, i)
valid_type = False
for j in buildoptions_typelist:
if i['type'] == j[0]:
self.assertIsInstance(i['value'], j[1])
assertKeyTypes(j[2], i)
valid_type = True
break
self.assertTrue(valid_type)
if i['name'] in buildopts_to_find:
self.assertEqual(i['value'], buildopts_to_find[i['name']])
buildopts_to_find.pop(i['name'], None)
self.assertDictEqual(buildopts_to_find, {})
# Check buildsystem_files
self.assertPathListEqual(res['buildsystem_files'], ['meson.build', 'sharedlib/meson.build', 'staticlib/meson.build'])
# Check dependencies
dependencies_to_find = ['threads']
for i in res['dependencies']:
assertKeyTypes(dependencies_typelist, i)
if i['name'] in dependencies_to_find:
dependencies_to_find.remove(i['name'])
self.assertListEqual(dependencies_to_find, [])
# Check projectinfo
self.assertDictEqual(res['projectinfo'], {'version': '1.2.3', 'descriptive_name': 'introspection', 'subprojects': []})
# Check targets
targets_to_find = {
'sharedTestLib': ('shared library', True, False),
'staticTestLib': ('static library', True, False),
'test1': ('executable', True, True),
'test2': ('executable', True, False),
'test3': ('executable', True, False),
}
for i in res['targets']:
assertKeyTypes(targets_typelist, i)
if i['name'] in targets_to_find:
tgt = targets_to_find[i['name']]
self.assertEqual(i['type'], tgt[0])
self.assertEqual(i['build_by_default'], tgt[1])
self.assertEqual(i['installed'], tgt[2])
targets_to_find.pop(i['name'], None)
for j in i['target_sources']:
assertKeyTypes(targets_sources_typelist, j)
self.assertDictEqual(targets_to_find, {})
def test_introspect_file_dump_equals_all(self):
testdir = os.path.join(self.unit_test_dir, '49 introspection')
self.init(testdir)
res_all = self.introspect('--all')
res_file = {}
root_keylist = [
'benchmarks',
'buildoptions',
'buildsystem_files',
'dependencies',
'installed',
'projectinfo',
'targets',
'tests',
]
infodir = os.path.join(self.builddir, 'meson-info')
self.assertPathExists(infodir)
for i in root_keylist:
curr = os.path.join(infodir, 'intro-{}.json'.format(i))
self.assertPathExists(curr)
with open(curr, 'r') as fp:
res_file[i] = json.load(fp)
self.assertEqual(res_all, res_file)
def test_introspect_config_update(self):
testdir = os.path.join(self.unit_test_dir, '49 introspection')
introfile = os.path.join(self.builddir, 'meson-info', 'intro-buildoptions.json')
self.init(testdir)
self.assertPathExists(introfile)
with open(introfile, 'r') as fp:
res1 = json.load(fp)
self.setconf('-Dcpp_std=c++14')
self.setconf('-Dbuildtype=release')
for idx, i in enumerate(res1):
if i['name'] == 'cpp_std':
res1[idx]['value'] = 'c++14'
if i['name'] == 'buildtype':
res1[idx]['value'] = 'release'
if i['name'] == 'optimization':
res1[idx]['value'] = '3'
if i['name'] == 'debug':
res1[idx]['value'] = False
with open(introfile, 'r') as fp:
res2 = json.load(fp)
self.assertListEqual(res1, res2)
class FailureTests(BasePlatformTests):
'''
@ -3548,7 +3754,7 @@ class DarwinTests(BasePlatformTests):
self.build()
targets = {}
for t in self.introspect('--targets'):
targets[t['name']] = t['filename']
targets[t['name']] = t['filename'][0] if isinstance(t['filename'], list) else t['filename']
self.assertEqual(self._get_darwin_versions(targets['some']), ('7.0.0', '7.0.0'))
self.assertEqual(self._get_darwin_versions(targets['noversion']), ('0.0.0', '0.0.0'))
self.assertEqual(self._get_darwin_versions(targets['onlyversion']), ('1.0.0', '1.0.0'))
@ -4197,7 +4403,7 @@ class LinuxlikeTests(BasePlatformTests):
break
self.assertIsInstance(docbook_target, dict)
ifile = self.introspect(['--target-files', 'generated-gdbus-docbook@cus'])[0]
self.assertEqual(t['filename'], 'gdbus/generated-gdbus-doc-' + os.path.basename(ifile))
self.assertListEqual([t['filename']], ['gdbus/generated-gdbus-doc-' + os.path.basename(ifile)])
def test_build_rpath(self):
if is_cygwin():

@ -0,0 +1,14 @@
project('introspection', ['c', 'cpp'], version: '1.2.3', default_options: ['cpp_std=c++11', 'buildtype=debug'])
dep1 = dependency('threads')
subdir('sharedlib')
subdir('staticlib')
t1 = executable('test1', 't1.cpp', link_with: [sharedlib], install: true)
t2 = executable('test2', 't2.cpp', link_with: [staticlib])
t3 = executable('test3', 't3.cpp', link_with: [sharedlib, staticlib], dependencies: [dep1])
test('test case 1', t1)
test('test case 2', t2)
benchmark('benchmark 1', t3)

@ -0,0 +1,2 @@
SRC_shared = ['shared.cpp']
sharedlib = shared_library('sharedTestLib', SRC_shared)

@ -0,0 +1,9 @@
#include "shared.hpp"
void SharedClass::doStuff() {
number++;
}
int SharedClass::getNumber() const {
return number;
}

@ -0,0 +1,10 @@
#pragma once
class SharedClass {
private:
int number = 42;
public:
SharedClass() = default;
void doStuff();
int getNumber() const;
};

@ -0,0 +1,2 @@
SRC_static = ['static.c']
staticlib = static_library('staticTestLib', SRC_static)

@ -0,0 +1,5 @@
#include "static.h"
int add_numbers(int a, int b) {
return a + b;
}

@ -0,0 +1,3 @@
#pragma once
int add_numbers(int a, int b);

@ -0,0 +1,13 @@
#include "sharedlib/shared.hpp"
int main() {
SharedClass cl1;
if(cl1.getNumber() != 42) {
return 1;
}
cl1.doStuff();
if(cl1.getNumber() != 43) {
return 2;
}
return 0;
}

@ -0,0 +1,8 @@
#include "staticlib/static.h"
int main() {
if(add_numbers(1, 2) != 3) {
return 1;
}
return 0;
}

@ -0,0 +1,16 @@
#include "sharedlib/shared.hpp"
#include "staticlib/static.h"
int main() {
for(int i = 0; i < 1000; add_numbers(i, 1)) {
SharedClass cl1;
if(cl1.getNumber() != 42) {
return 1;
}
cl1.doStuff();
if(cl1.getNumber() != 43) {
return 2;
}
}
return 0;
}
Loading…
Cancel
Save