summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2018-11-27 22:18:13 +0200
committerGitHub <noreply@github.com>2018-11-27 22:18:13 +0200
commit270f2395d6f4d3f1134393bf18c8eb3d9f1e5332 (patch)
tree8717aa86b33607d6c71be4cfa1d2e553538cfbf3
parent2c91ca3d6a39692071a2b9f9c23b7c378a3e837e (diff)
parentf954eee90669f4c9ac0f458975a37ace28ba3a47 (diff)
downloadmeson-270f2395d6f4d3f1134393bf18c8eb3d9f1e5332.tar.gz
Merge pull request #4191 from trilader/feature/projectinfo-from-source
mintro: Allow introspect --projectinfo without build directory
-rw-r--r--docs/markdown/snippets/introspect_projectinfo.md35
-rw-r--r--mesonbuild/astinterpreter.py94
-rw-r--r--mesonbuild/mintro.py77
-rw-r--r--mesonbuild/rewriter.py2
-rwxr-xr-xrun_unittests.py34
5 files changed, 210 insertions, 32 deletions
diff --git a/docs/markdown/snippets/introspect_projectinfo.md b/docs/markdown/snippets/introspect_projectinfo.md
new file mode 100644
index 000000000..40558b88e
--- /dev/null
+++ b/docs/markdown/snippets/introspect_projectinfo.md
@@ -0,0 +1,35 @@
+## `introspect --projectinfo` can now be used without configured build directory
+
+This allows IDE integration to get information about the project before the user has configured a build directory.
+
+Before you could use `meson.py introspect --projectinfo build-directory`.
+Now you also can use `meson.py introspect --projectinfo project-dir/meson.build`.
+
+The output is similiar to the output with a build directory but additionally also includes information from `introspect --buildsystem-files`.
+
+For example `meson.py introspect --projectinfo test\ cases/common/47\ subproject\ options/meson.build`
+This outputs (pretty printed for readability):
+```
+{
+ "buildsystem_files": [
+ "meson_options.txt",
+ "meson.build"
+ ],
+ "name": "suboptions",
+ "version": null,
+ "descriptive_name": "suboptions",
+ "subprojects": [
+ {
+ "buildsystem_files": [
+ "subprojects/subproject/meson_options.txt",
+ "subprojects/subproject/meson.build"
+ ],
+ "name": "subproject",
+ "version": "undefined",
+ "descriptive_name": "subproject"
+ }
+ ]
+}
+```
+
+Both usages now include a new `descriptive_name` property which always shows the name set in the project.
diff --git a/mesonbuild/astinterpreter.py b/mesonbuild/astinterpreter.py
index 32d08450c..a447a5597 100644
--- a/mesonbuild/astinterpreter.py
+++ b/mesonbuild/astinterpreter.py
@@ -18,7 +18,7 @@
from . import interpreterbase, mlog, mparser, mesonlib
from . import environment
-from .interpreterbase import InterpreterException, InvalidArguments
+from .interpreterbase import InterpreterException, InvalidArguments, BreakRequest, ContinueRequest
import os, sys
@@ -46,7 +46,6 @@ REMOVE_SOURCE = 1
class AstInterpreter(interpreterbase.InterpreterBase):
def __init__(self, source_root, subdir):
super().__init__(source_root, subdir)
- self.asts = {}
self.funcs.update({'project': self.func_do_nothing,
'test': self.func_do_nothing,
'benchmark': self.func_do_nothing,
@@ -76,7 +75,72 @@ class AstInterpreter(interpreterbase.InterpreterBase):
'vcs_tag': self.func_do_nothing,
'add_languages': self.func_do_nothing,
'declare_dependency': self.func_do_nothing,
- 'files': self.func_files,
+ 'files': self.func_do_nothing,
+ 'executable': self.func_do_nothing,
+ 'static_library': self.func_do_nothing,
+ 'shared_library': self.func_do_nothing,
+ 'library': self.func_do_nothing,
+ 'build_target': self.func_do_nothing,
+ 'custom_target': self.func_do_nothing,
+ 'run_target': self.func_do_nothing,
+ 'subdir': self.func_do_nothing,
+ 'set_variable': self.func_do_nothing,
+ 'get_variable': self.func_do_nothing,
+ 'is_variable': self.func_do_nothing,
+ })
+
+ def func_do_nothing(self, node, args, kwargs):
+ return True
+
+ def method_call(self, node):
+ return True
+
+ def evaluate_arithmeticstatement(self, cur):
+ return 0
+
+ def evaluate_plusassign(self, node):
+ return 0
+
+ def evaluate_indexing(self, node):
+ return 0
+
+ def unknown_function_called(self, func_name):
+ pass
+
+ def reduce_arguments(self, args):
+ assert(isinstance(args, mparser.ArgumentNode))
+ if args.incorrect_order():
+ raise InvalidArguments('All keyword arguments must be after positional arguments.')
+ return args.arguments, args.kwargs
+
+ def evaluate_comparison(self, node):
+ return False
+
+ def evaluate_foreach(self, node):
+ try:
+ self.evaluate_codeblock(node.block)
+ except ContinueRequest:
+ pass
+ except BreakRequest:
+ pass
+
+ def evaluate_if(self, node):
+ for i in node.ifs:
+ self.evaluate_codeblock(i.block)
+ if not isinstance(node.elseblock, mparser.EmptyNode):
+ self.evaluate_codeblock(node.elseblock)
+
+ def get_variable(self, varname):
+ return 0
+
+ def assignment(self, node):
+ pass
+
+class RewriterInterpreter(AstInterpreter):
+ def __init__(self, source_root, subdir):
+ super().__init__(source_root, subdir)
+ self.asts = {}
+ self.funcs.update({'files': self.func_files,
'executable': self.func_executable,
'static_library': self.func_static_lib,
'shared_library': self.func_shared_lib,
@@ -90,12 +154,6 @@ class AstInterpreter(interpreterbase.InterpreterBase):
'is_variable': self.func_is_variable,
})
- def func_do_nothing(self, node, args, kwargs):
- return True
-
- def method_call(self, node):
- return True
-
def func_executable(self, node, args, kwargs):
if args[0] == self.targetname:
if self.operation == ADD_SOURCE:
@@ -147,21 +205,6 @@ class AstInterpreter(interpreterbase.InterpreterBase):
return [args]
return args
- def evaluate_arithmeticstatement(self, cur):
- return 0
-
- def evaluate_plusassign(self, node):
- return 0
-
- def evaluate_indexing(self, node):
- return 0
-
- def reduce_arguments(self, args):
- assert(isinstance(args, mparser.ArgumentNode))
- if args.incorrect_order():
- raise InvalidArguments('All keyword arguments must be after positional arguments.')
- return args.arguments, args.kwargs
-
def transform(self):
self.load_root_meson_file()
self.asts[''] = self.ast
@@ -181,9 +224,6 @@ class AstInterpreter(interpreterbase.InterpreterBase):
self.filename = filename
self.transform()
- def unknown_function_called(self, func_name):
- mlog.warning('Unknown function called: ' + func_name)
-
def add_source_to_target(self, node, args, kwargs):
namespan = node.args.arguments[0].bytespan
buildfilename = os.path.join(self.source_root, self.subdir, environment.build_filename)
diff --git a/mesonbuild/mintro.py b/mesonbuild/mintro.py
index 4062299c0..48ec20faf 100644
--- a/mesonbuild/mintro.py
+++ b/mesonbuild/mintro.py
@@ -22,6 +22,9 @@ project files and don't need this info."""
import json
from . import build, mtest, coredata as cdata
from . import mesonlib
+from . import astinterpreter
+from . import mparser
+from .interpreterbase import InvalidArguments
from .backend import ninjabackend
import sys, os
import pathlib
@@ -178,14 +181,18 @@ def add_keys(optlist, options, section):
optdict['description'] = opt.description
optlist.append(optdict)
-def list_buildsystem_files(builddata):
- src_dir = builddata.environment.get_source_dir()
+def find_buildsystem_files_list(src_dir):
# I feel dirty about this. But only slightly.
filelist = []
for root, _, files in os.walk(src_dir):
for f in files:
if f == 'meson.build' or f == 'meson_options.txt':
filelist.append(os.path.relpath(os.path.join(root, f), src_dir))
+ return filelist
+
+def list_buildsystem_files(builddata):
+ src_dir = builddata.environment.get_source_dir()
+ filelist = find_buildsystem_files_list(src_dir)
print(json.dumps(filelist))
def list_deps(coredata):
@@ -219,19 +226,81 @@ def list_tests(testdata):
print(json.dumps(result))
def list_projinfo(builddata):
- result = {'name': builddata.project_name, 'version': builddata.project_version}
+ result = {'version': builddata.project_version,
+ 'descriptive_name': builddata.project_name}
subprojects = []
for k, v in builddata.subprojects.items():
c = {'name': k,
- 'version': v}
+ 'version': v,
+ 'descriptive_name': builddata.projects.get(k)}
subprojects.append(c)
result['subprojects'] = subprojects
print(json.dumps(result))
+class ProjectInfoInterperter(astinterpreter.AstInterpreter):
+ def __init__(self, source_root, subdir):
+ super().__init__(source_root, subdir)
+ self.funcs.update({'project': self.func_project})
+ self.project_name = None
+ self.project_version = None
+
+ def func_project(self, node, args, kwargs):
+ if len(args) < 1:
+ raise InvalidArguments('Not enough arguments to project(). Needs at least the project name.')
+ self.project_name = args[0]
+ self.project_version = kwargs.get('version', 'undefined')
+ if isinstance(self.project_version, mparser.ElementaryNode):
+ self.project_version = self.project_version.value
+
+ def set_variable(self, varname, variable):
+ pass
+
+ def analyze(self):
+ self.load_root_meson_file()
+ self.sanity_check_ast()
+ self.parse_project()
+ self.run()
+
+def list_projinfo_from_source(sourcedir):
+ files = find_buildsystem_files_list(sourcedir)
+
+ result = {'buildsystem_files': []}
+ subprojects = {}
+
+ for f in files:
+ f = f.replace('\\', '/')
+ if f == 'meson.build':
+ interpreter = ProjectInfoInterperter(sourcedir, '')
+ interpreter.analyze()
+ version = None
+ if interpreter.project_version is str:
+ version = interpreter.project_version
+ result.update({'version': version, 'descriptive_name': interpreter.project_name})
+ result['buildsystem_files'].append(f)
+ elif f.startswith('subprojects/'):
+ subproject_id = f.split('/')[1]
+ subproject = subprojects.setdefault(subproject_id, {'buildsystem_files': []})
+ subproject['buildsystem_files'].append(f)
+ if f.count('/') == 2 and f.endswith('meson.build'):
+ interpreter = ProjectInfoInterperter(os.path.join(sourcedir, 'subprojects', subproject_id), '')
+ interpreter.analyze()
+ subproject.update({'name': subproject_id, 'version': interpreter.project_version, 'descriptive_name': interpreter.project_name})
+ else:
+ result['buildsystem_files'].append(f)
+
+ subprojects = [obj for name, obj in subprojects.items()]
+ result['subprojects'] = subprojects
+ print(json.dumps(result))
+
def run(options):
datadir = 'meson-private'
if options.builddir is not None:
datadir = os.path.join(options.builddir, datadir)
+ if options.builddir.endswith('/meson.build') or options.builddir.endswith('\\meson.build') or options.builddir == 'meson.build':
+ if options.projectinfo:
+ sourcedir = '.' if options.builddir == 'meson.build' else options.builddir[:-11]
+ list_projinfo_from_source(sourcedir)
+ 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.')
diff --git a/mesonbuild/rewriter.py b/mesonbuild/rewriter.py
index 5da8c8903..37ed7efd6 100644
--- a/mesonbuild/rewriter.py
+++ b/mesonbuild/rewriter.py
@@ -41,7 +41,7 @@ def run(options):
if options.target is None or options.filename is None:
sys.exit("Must specify both target and filename.")
print('This tool is highly experimental, use with care.')
- rewriter = mesonbuild.astinterpreter.AstInterpreter(options.sourcedir, '')
+ rewriter = mesonbuild.astinterpreter.RewriterInterpreter(options.sourcedir, '')
try:
if options.commands[0] == 'add':
rewriter.add_source(options.target, options.filename)
diff --git a/run_unittests.py b/run_unittests.py
index 8351dadb7..d23918cdc 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -1165,6 +1165,13 @@ class BasePlatformTests(unittest.TestCase):
universal_newlines=True)
return json.loads(out)
+ def introspect_directory(self, directory, args):
+ if isinstance(args, str):
+ args = [args]
+ out = subprocess.check_output(self.mintro_command + args + [directory],
+ universal_newlines=True)
+ return json.loads(out)
+
def assertPathEqual(self, path1, path2):
'''
Handles a lot of platform-specific quirks related to paths such as
@@ -2913,6 +2920,33 @@ recommended as it is not supported on some platforms''')
'target2-id', '@other')
self.assertEqual('81d46d1@@target2-id@other', target_id)
+ def test_introspect_projectinfo_without_configured_build(self):
+ testfile = os.path.join(self.common_test_dir, '36 run program', 'meson.build')
+ res = self.introspect_directory(testfile, '--projectinfo')
+ self.assertEqual(set(res['buildsystem_files']), set(['meson.build']))
+ self.assertEqual(res['version'], None)
+ self.assertEqual(res['descriptive_name'], 'run command')
+ self.assertEqual(res['subprojects'], [])
+
+ testfile = os.path.join(self.common_test_dir, '44 options', 'meson.build')
+ res = self.introspect_directory(testfile, '--projectinfo')
+ self.assertEqual(set(res['buildsystem_files']), set(['meson_options.txt', 'meson.build']))
+ self.assertEqual(res['version'], None)
+ self.assertEqual(res['descriptive_name'], 'options')
+ self.assertEqual(res['subprojects'], [])
+
+ testfile = os.path.join(self.common_test_dir, '47 subproject options', 'meson.build')
+ res = self.introspect_directory(testfile, '--projectinfo')
+ self.assertEqual(set(res['buildsystem_files']), set(['meson_options.txt', 'meson.build']))
+ self.assertEqual(res['version'], None)
+ self.assertEqual(res['descriptive_name'], 'suboptions')
+ self.assertEqual(len(res['subprojects']), 1)
+ subproject_files = set(f.replace('\\', '/') for f in res['subprojects'][0]['buildsystem_files'])
+ self.assertEqual(subproject_files, set(['subprojects/subproject/meson_options.txt', 'subprojects/subproject/meson.build']))
+ self.assertEqual(res['subprojects'][0]['name'], 'subproject')
+ self.assertEqual(res['subprojects'][0]['version'], 'undefined')
+ self.assertEqual(res['subprojects'][0]['descriptive_name'], 'subproject')
+
class FailureTests(BasePlatformTests):
'''