Merge pull request #4732 from mensinda/introMesonInfo2

mintro: Added `meson-info.json` introspection information about the latest meson run
pull/4745/head
Jussi Pakkanen 6 years ago committed by GitHub
commit 3bf2ca483e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      docs/markdown/snippets/introspect_meson_info.md
  2. 4
      mesonbuild/mconf.py
  3. 189
      mesonbuild/mintro.py
  4. 18
      mesonbuild/msetup.py
  5. 14
      run_unittests.py

@ -0,0 +1,6 @@
## Added the `meson-info.json` introspection file
Meson now generates a `meson-info.json` file in the `meson-info` directory
to provide introspection information about the latest meson run. This file
is updated when the build configuration is changed and the build files are
(re)generated.

@ -148,6 +148,7 @@ class Conf:
def run(options): def run(options):
coredata.parse_cmd_line_options(options) coredata.parse_cmd_line_options(options)
builddir = os.path.abspath(os.path.realpath(options.builddir)) builddir = os.path.abspath(os.path.realpath(options.builddir))
c = None
try: try:
c = Conf(builddir) c = Conf(builddir)
save = False save = False
@ -163,7 +164,10 @@ def run(options):
if save: if save:
c.save() c.save()
mintro.update_build_options(c.coredata, c.build.environment.info_dir) mintro.update_build_options(c.coredata, c.build.environment.info_dir)
mintro.write_meson_info_file(c.build, [])
except ConfException as e: except ConfException as e:
print('Meson configurator encountered an error:') print('Meson configurator encountered an error:')
if c is not None and c.build is not None:
mintro.write_meson_info_file(c.build, [e])
raise e raise e
return 0 return 0

@ -33,25 +33,64 @@ from .backend import backends
import sys, os import sys, os
import pathlib import pathlib
def get_meson_info_file(info_dir: str):
return os.path.join(info_dir, 'meson-info.json')
def get_meson_introspection_version():
return '1.0.0'
def get_meson_introspection_types(coredata: cdata.CoreData = None, builddata: build.Build = None, backend: backends.Backend = None):
if backend and builddata:
benchmarkdata = backend.create_test_serialisation(builddata.get_benchmarks())
testdata = backend.create_test_serialisation(builddata.get_tests())
installdata = backend.create_install_data()
else:
benchmarkdata = testdata = installdata = None
return {
'benchmarks': {
'func': lambda: list_benchmarks(benchmarkdata),
'desc': 'List all benchmarks.',
},
'buildoptions': {
'func': lambda: list_buildoptions(coredata),
'desc': 'List all build options.',
},
'buildsystem_files': {
'func': lambda: list_buildsystem_files(builddata),
'desc': 'List files that make up the build system.',
'key': 'buildsystem-files',
},
'dependencies': {
'func': lambda: list_deps(coredata),
'desc': 'List external dependencies.',
},
'installed': {
'func': lambda: list_installed(installdata),
'desc': 'List all installed files and directories.',
},
'projectinfo': {
'func': lambda: list_projinfo(builddata),
'desc': 'Information about projects.',
},
'targets': {
'func': lambda: list_targets(builddata, installdata, backend),
'desc': 'List top level targets.',
},
'tests': {
'func': lambda: list_tests(testdata),
'desc': 'List all unit tests.',
}
}
def add_arguments(parser): def add_arguments(parser):
parser.add_argument('--targets', action='store_true', dest='list_targets', default=False, intro_types = get_meson_introspection_types()
help='List top level targets.') for key, val in intro_types.items():
parser.add_argument('--installed', action='store_true', dest='list_installed', default=False, flag = '--' + val.get('key', key)
help='List all installed files and directories.') parser.add_argument(flag, action='store_true', dest=key, default=False, help=val['desc'])
parser.add_argument('--target-files', action='store', dest='target_files', default=None, parser.add_argument('--target-files', action='store', dest='target_files', default=None,
help='List source files for a given target.') help='List source files for a given target.')
parser.add_argument('--buildsystem-files', action='store_true', dest='buildsystem_files', default=False,
help='List files that make up the build system.')
parser.add_argument('--buildoptions', action='store_true', dest='buildoptions', default=False,
help='List all build options.')
parser.add_argument('--tests', action='store_true', dest='tests', default=False,
help='List all unit tests.')
parser.add_argument('--benchmarks', action='store_true', dest='benchmarks', default=False,
help='List all benchmarks.')
parser.add_argument('--dependencies', action='store_true', dest='dependencies', default=False,
help='List external dependencies.')
parser.add_argument('--projectinfo', action='store_true', dest='projectinfo', default=False,
help='Information about projects.')
parser.add_argument('--backend', choices=cdata.backendlist, dest='backend', default='ninja', parser.add_argument('--backend', choices=cdata.backendlist, dest='backend', default='ninja',
help='The backend to use for the --buildoptions introspection.') help='The backend to use for the --buildoptions introspection.')
parser.add_argument('-a', '--all', action='store_true', dest='all', default=False, parser.add_argument('-a', '--all', action='store_true', dest='all', default=False,
@ -74,7 +113,7 @@ def list_installed(installdata):
res[path] = os.path.join(installdata.prefix, installdir, os.path.basename(path)) res[path] = os.path.join(installdata.prefix, installdir, os.path.basename(path))
for path, installpath, unused_custom_install_mode in installdata.man: for path, installpath, unused_custom_install_mode in installdata.man:
res[path] = os.path.join(installdata.prefix, installpath) res[path] = os.path.join(installdata.prefix, installpath)
return ('installed', res) return res
def list_targets(builddata: build.Build, installdata, backend: backends.Backend): def list_targets(builddata: build.Build, installdata, backend: backends.Backend):
tlist = [] tlist = []
@ -110,7 +149,7 @@ def list_targets(builddata: build.Build, installdata, backend: backends.Backend)
else: else:
t['installed'] = False t['installed'] = False
tlist.append(t) tlist.append(t)
return ('targets', tlist) return tlist
class BuildoptionsOptionHelper: class BuildoptionsOptionHelper:
# mimic an argparse namespace # mimic an argparse namespace
@ -257,8 +296,7 @@ def list_buildoptions_from_source(sourcedir, backend, indent):
intr.analyze() intr.analyze()
# Reenable logging just in case # Reenable logging just in case
mlog.enable() mlog.enable()
buildoptions = list_buildoptions(intr.coredata)[1] print(json.dumps(list_buildoptions(intr.coredata), indent=indent))
print(json.dumps(buildoptions, indent=indent))
def list_target_files(target_name, targets, builddata: build.Build): def list_target_files(target_name, targets, builddata: build.Build):
result = [] result = []
@ -279,7 +317,7 @@ def list_target_files(target_name, targets, builddata: build.Build):
# TODO Remove this line in a future PR with other breaking changes # 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)) result = list(map(lambda x: os.path.relpath(x, builddata.environment.get_source_dir()), result))
return ('target_files', result) return result
def list_buildoptions(coredata: cdata.CoreData): def list_buildoptions(coredata: cdata.CoreData):
optlist = [] optlist = []
@ -312,7 +350,7 @@ def list_buildoptions(coredata: cdata.CoreData):
add_keys(optlist, dir_options, 'directory') add_keys(optlist, dir_options, 'directory')
add_keys(optlist, coredata.user_options, 'user') add_keys(optlist, coredata.user_options, 'user')
add_keys(optlist, test_options, 'test') add_keys(optlist, test_options, 'test')
return ('buildoptions', optlist) return optlist
def add_keys(optlist, options, section): def add_keys(optlist, options, section):
keys = list(options.keys()) keys = list(options.keys())
@ -349,7 +387,7 @@ def find_buildsystem_files_list(src_dir):
def list_buildsystem_files(builddata: build.Build): def list_buildsystem_files(builddata: build.Build):
src_dir = builddata.environment.get_source_dir() src_dir = builddata.environment.get_source_dir()
filelist = find_buildsystem_files_list(src_dir) filelist = find_buildsystem_files_list(src_dir)
return ('buildsystem_files', filelist) return filelist
def list_deps(coredata: cdata.CoreData): def list_deps(coredata: cdata.CoreData):
result = [] result = []
@ -358,7 +396,7 @@ def list_deps(coredata: cdata.CoreData):
result += [{'name': d.name, result += [{'name': d.name,
'compile_args': d.get_compile_args(), 'compile_args': d.get_compile_args(),
'link_args': d.get_link_args()}] 'link_args': d.get_link_args()}]
return ('dependencies', result) return result
def get_test_list(testdata): def get_test_list(testdata):
result = [] result = []
@ -382,10 +420,10 @@ def get_test_list(testdata):
return result return result
def list_tests(testdata): def list_tests(testdata):
return ('tests', get_test_list(testdata)) return get_test_list(testdata)
def list_benchmarks(benchdata): def list_benchmarks(benchdata):
return ('benchmarks', get_test_list(benchdata)) return get_test_list(benchdata)
def list_projinfo(builddata: build.Build): def list_projinfo(builddata: build.Build):
result = {'version': builddata.project_version, result = {'version': builddata.project_version,
@ -397,7 +435,7 @@ def list_projinfo(builddata: build.Build):
'descriptive_name': builddata.projects.get(k)} 'descriptive_name': builddata.projects.get(k)}
subprojects.append(c) subprojects.append(c)
result['subprojects'] = subprojects result['subprojects'] = subprojects
return ('projectinfo', result) return result
class ProjectInfoInterperter(astinterpreter.AstInterpreter): class ProjectInfoInterperter(astinterpreter.AstInterpreter):
def __init__(self, source_root, subdir): def __init__(self, source_root, subdir):
@ -482,30 +520,21 @@ def run(options):
results = [] results = []
toextract = [] toextract = []
intro_types = get_meson_introspection_types()
for i in intro_types.keys():
if options.all or getattr(options, i, False):
toextract += [i]
if options.all or options.benchmarks: # Handle the one option that does not have its own JSON file (meybe deprecate / remove this?)
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: if options.target_files is not None:
targets_file = os.path.join(infodir, 'intro-targets.json') targets_file = os.path.join(infodir, 'intro-targets.json')
with open(targets_file, 'r') as fp: with open(targets_file, 'r') as fp:
targets = json.load(fp) targets = json.load(fp)
builddata = build.load(options.builddir) # TODO remove this in a breaking changes PR builddata = build.load(options.builddir) # TODO remove this in a breaking changes PR
results += [list_target_files(options.target_files, targets, builddata)] results += [('target_files', list_target_files(options.target_files, targets, builddata))]
if options.all or options.tests:
toextract += ['tests']
# Extract introspection information from JSON
for i in toextract: for i in toextract:
curr = os.path.join(infodir, 'intro-{}.json'.format(i)) curr = os.path.join(infodir, 'intro-{}.json'.format(i))
if not os.path.isfile(curr): if not os.path.isfile(curr):
@ -527,7 +556,10 @@ def run(options):
print(json.dumps(out, indent=indent)) print(json.dumps(out, indent=indent))
return 0 return 0
updated_introspection_files = []
def write_intro_info(intro_info, info_dir): def write_intro_info(intro_info, info_dir):
global updated_introspection_files
for i in intro_info: for i in intro_info:
out_file = os.path.join(info_dir, 'intro-{}.json'.format(i[0])) out_file = os.path.join(info_dir, 'intro-{}.json'.format(i[0]))
tmp_file = os.path.join(info_dir, 'tmp_dump.json') tmp_file = os.path.join(info_dir, 'tmp_dump.json')
@ -535,29 +567,70 @@ def write_intro_info(intro_info, info_dir):
json.dump(i[1], fp) json.dump(i[1], fp)
fp.flush() # Not sure if this is needed fp.flush() # Not sure if this is needed
os.replace(tmp_file, out_file) os.replace(tmp_file, out_file)
updated_introspection_files += [i[0]]
def generate_introspection_file(builddata: build.Build, backend: backends.Backend): def generate_introspection_file(builddata: build.Build, backend: backends.Backend):
coredata = builddata.environment.get_coredata() coredata = builddata.environment.get_coredata()
benchmarkdata = backend.create_test_serialisation(builddata.get_benchmarks()) intro_types = get_meson_introspection_types(coredata=coredata, builddata=builddata, backend=backend)
testdata = backend.create_test_serialisation(builddata.get_tests()) intro_info = []
installdata = backend.create_install_data()
intro_info = [ for key, val in intro_types.items():
list_benchmarks(benchmarkdata), intro_info += [(key, val['func']())]
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) write_intro_info(intro_info, builddata.environment.info_dir)
def update_build_options(coredata: cdata.CoreData, info_dir): def update_build_options(coredata: cdata.CoreData, info_dir):
intro_info = [ intro_info = [
list_buildoptions(coredata) ('buildoptions', list_buildoptions(coredata))
] ]
write_intro_info(intro_info, info_dir) write_intro_info(intro_info, info_dir)
def split_version_string(version: str):
vers_list = version.split('.')
return {
'full': version,
'major': int(vers_list[0] if len(vers_list) > 0 else 0),
'minor': int(vers_list[1] if len(vers_list) > 1 else 0),
'patch': int(vers_list[2] if len(vers_list) > 2 else 0)
}
def write_meson_info_file(builddata: build.Build, errors: list, build_files_updated: bool = False):
global updated_introspection_files
info_dir = builddata.environment.info_dir
info_file = get_meson_info_file(info_dir)
intro_types = get_meson_introspection_types()
intro_info = {}
for i in intro_types.keys():
intro_info[i] = {
'file': 'intro-{}.json'.format(i),
'updated': i in updated_introspection_files
}
info_data = {
'meson_version': split_version_string(cdata.version),
'directories': {
'source': builddata.environment.get_source_dir(),
'build': builddata.environment.get_build_dir(),
'info': info_dir,
},
'introspection': {
'version': split_version_string(get_meson_introspection_version()),
'information': intro_info,
},
'build_files_updated': build_files_updated,
}
if len(errors) > 0:
info_data['error'] = True
info_data['error_list'] = [x if isinstance(x, str) else str(x) for x in errors]
else:
info_data['error'] = False
# Write the data to disc
tmp_file = os.path.join(info_dir, 'tmp_dump.json')
with open(tmp_file, 'w') as fp:
json.dump(info_data, fp)
fp.flush()
os.replace(tmp_file, info_file)

@ -185,11 +185,15 @@ class MesonApp:
mlog.log('Target machine cpu:', mlog.bold(intr.builtin['target_machine'].cpu_method([], {}))) mlog.log('Target machine cpu:', mlog.bold(intr.builtin['target_machine'].cpu_method([], {})))
mlog.log('Build machine cpu family:', mlog.bold(intr.builtin['build_machine'].cpu_family_method([], {}))) mlog.log('Build machine cpu family:', mlog.bold(intr.builtin['build_machine'].cpu_family_method([], {})))
mlog.log('Build machine cpu:', mlog.bold(intr.builtin['build_machine'].cpu_method([], {}))) mlog.log('Build machine cpu:', mlog.bold(intr.builtin['build_machine'].cpu_method([], {})))
if self.options.profile: try:
fname = os.path.join(self.build_dir, 'meson-private', 'profile-interpreter.log') if self.options.profile:
profile.runctx('intr.run()', globals(), locals(), filename=fname) fname = os.path.join(self.build_dir, 'meson-private', 'profile-interpreter.log')
else: profile.runctx('intr.run()', globals(), locals(), filename=fname)
intr.run() else:
intr.run()
except Exception as e:
mintro.write_meson_info_file(b, [e])
raise
# Print all default option values that don't match the current value # Print all default option values that don't match the current value
for def_opt_name, def_opt_value, cur_opt_value in intr.get_non_matching_default_options(): for def_opt_name, def_opt_value, cur_opt_value in intr.get_non_matching_default_options():
mlog.log('Option', mlog.bold(def_opt_name), 'is:', mlog.log('Option', mlog.bold(def_opt_name), 'is:',
@ -224,7 +228,9 @@ class MesonApp:
profile.runctx('mintro.generate_introspection_file(b, intr.backend)', globals(), locals(), filename=fname) profile.runctx('mintro.generate_introspection_file(b, intr.backend)', globals(), locals(), filename=fname)
else: else:
mintro.generate_introspection_file(b, intr.backend) mintro.generate_introspection_file(b, intr.backend)
except: mintro.write_meson_info_file(b, [], True)
except Exception as e:
mintro.write_meson_info_file(b, [e])
if 'cdf' in locals(): if 'cdf' in locals():
old_cdf = cdf + '.prev' old_cdf = cdf + '.prev'
if os.path.exists(old_cdf): if os.path.exists(old_cdf):

@ -3291,6 +3291,20 @@ recommended as it is not supported on some platforms''')
self.assertEqual(res_all, res_file) self.assertEqual(res_all, res_file)
def test_introspect_meson_info(self):
testdir = os.path.join(self.unit_test_dir, '49 introspection')
introfile = os.path.join(self.builddir, 'meson-info', 'meson-info.json')
self.init(testdir)
self.assertPathExists(introfile)
with open(introfile, 'r') as fp:
res1 = json.load(fp)
for i in ['meson_version', 'directories', 'introspection', 'build_files_updated', 'error']:
self.assertIn(i, res1)
self.assertEqual(res1['error'], False)
self.assertEqual(res1['build_files_updated'], True)
def test_introspect_config_update(self): def test_introspect_config_update(self):
testdir = os.path.join(self.unit_test_dir, '49 introspection') testdir = os.path.join(self.unit_test_dir, '49 introspection')
introfile = os.path.join(self.builddir, 'meson-info', 'intro-buildoptions.json') introfile = os.path.join(self.builddir, 'meson-info', 'intro-buildoptions.json')

Loading…
Cancel
Save