mirror of
https://github.com/AdaCore/langkit.git
synced 2026-02-12 12:28:12 -08:00
So far, ManageScript assumed that the lang_source_dir is the directory that contains the Python source file which defines the ManageScript subclass. This is wrong for testcases, as there is generally only one ManageScript subclass, in the testsute/python_support/utils.py source file. Introduce a new "root_dir" ManageScript constructor argument to override this behavior, and use it in the testsuite appropriately. TN: T914-010
364 lines
13 KiB
Python
364 lines
13 KiB
Python
import os
|
|
import os.path as P
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
import langkit
|
|
import langkit.compile_context
|
|
from langkit.compile_context import CompileCtx, UnparseScript
|
|
from langkit.diagnostics import DiagnosticError, Diagnostics, WarningSet
|
|
from langkit.libmanage import ManageScript
|
|
|
|
from drivers.valgrind import valgrind_cmd
|
|
|
|
|
|
Diagnostics.blacklisted_paths.append(P.dirname(P.abspath(__file__)))
|
|
|
|
|
|
default_warning_set = WarningSet()
|
|
|
|
# We don't want to be forced to provide dummy docs for nodes and public
|
|
# properties in testcases.
|
|
default_warning_set.disable(WarningSet.undocumented_nodes)
|
|
default_warning_set.disable(WarningSet.undocumented_public_properties)
|
|
|
|
pretty_print = bool(int(os.environ.get('LANGKIT_PRETTY_PRINT', '0')))
|
|
|
|
project_template = """
|
|
with "libfoolang";
|
|
|
|
project Gen is
|
|
for Languages use ("Ada");
|
|
for Source_Dirs use (".");
|
|
for Object_Dir use "obj";
|
|
for Main use ({main_sources});
|
|
|
|
package Compiler is
|
|
for Default_Switches ("Ada") use
|
|
("-g", "-O0", "-gnata", "-gnatwae", "-gnatyg");
|
|
end Compiler;
|
|
end Gen;
|
|
"""
|
|
|
|
|
|
valgrind_enabled = bool(os.environ.get('VALGRIND_ENABLED'))
|
|
|
|
|
|
# Determine where to find the root directory for Langkit sources
|
|
langkit_root = os.environ.get('LANGKIT_ROOT_DIR')
|
|
if not langkit_root:
|
|
test_dir = P.dirname(P.abspath(__file__))
|
|
testsuite_dir = P.dirname(test_dir)
|
|
langkit_root = P.dirname(testsuite_dir)
|
|
|
|
|
|
# When unparsing the concrete syntax, name of the file to write
|
|
unparse_destination = 'concrete_syntax.lkt'
|
|
unparse_script = ('to:{},import:lexer_example,grammar,nodes'
|
|
.format(unparse_destination))
|
|
unparse_all_script = 'to:{},lexer,grammar,nodes'.format(unparse_destination)
|
|
|
|
|
|
def prepare_context(grammar=None, lexer=None, lkt_file=None,
|
|
warning_set=default_warning_set,
|
|
symbol_canonicalizer=None, show_property_logging=False,
|
|
types_from_lkt=False, lkt_semantic_checks=False):
|
|
"""
|
|
Create a compile context and prepare the build directory for code
|
|
generation.
|
|
|
|
:param langkit.parsers.Grammar grammar: The language grammar to use for
|
|
this context.
|
|
|
|
:param langkit.lexer.Lexer lexer: The language lexer to use for this
|
|
context.
|
|
|
|
:param str|None lkt_file: If provided, file from which to read the Lkt
|
|
language spec.
|
|
|
|
:param WarningSet warning_set: Set of warnings to emit.
|
|
|
|
:param langkit.compile_context.LibraryEntity|None symbol_canonicalizer:
|
|
Symbol canonicalizer to use for this context, if any.
|
|
|
|
:param bool show_property_logging: See CompileCtx.show_property_logging.
|
|
|
|
:param bool types_from_lkt: See CompileCtx.types_from_lkt.
|
|
"""
|
|
|
|
# Have a clean build directory
|
|
if P.exists('build'):
|
|
shutil.rmtree('build')
|
|
os.mkdir('build')
|
|
|
|
# Try to emit code
|
|
ctx = CompileCtx(lang_name='Foo', short_name='Foo', lexer=lexer,
|
|
grammar=grammar,
|
|
symbol_canonicalizer=symbol_canonicalizer,
|
|
show_property_logging=show_property_logging,
|
|
lkt_file=lkt_file,
|
|
types_from_lkt=types_from_lkt,
|
|
lkt_semantic_checks=lkt_semantic_checks)
|
|
ctx.warnings = warning_set
|
|
ctx.pretty_print = pretty_print
|
|
|
|
return ctx
|
|
|
|
|
|
def emit_and_print_errors(grammar=None, lexer=None, lkt_file=None,
|
|
warning_set=default_warning_set,
|
|
generate_unparser=False, symbol_canonicalizer=None,
|
|
unparse_script=None):
|
|
"""
|
|
Compile and emit code for CTX. Return the compile context if this was
|
|
successful, None otherwise.
|
|
|
|
:param langkit.parsers.Grammar grammar_fn: The language grammar to use.
|
|
|
|
:param langkit.lexer.Lexer lexer: The lexer to use along with the grammar.
|
|
Use `lexer_example.foo_lexer` if left to None.
|
|
|
|
:param str|None lkt_file: If provided, file from which to read the Lkt
|
|
language spec.
|
|
|
|
:param WarningSet warning_set: Set of warnings to emit.
|
|
|
|
:param bool generate_unparser: Whether to generate unparser.
|
|
|
|
:param langkit.compile_context.LibraryEntity|None symbol_canonicalizer:
|
|
Symbol canoncalizes to use for this context, if any.
|
|
|
|
:rtype: None|langkit.compile_context.CompileCtx
|
|
|
|
:param None|str unparse_script: Script to unparse the language spec.
|
|
"""
|
|
|
|
try:
|
|
ctx = prepare_context(grammar, lexer, lkt_file, warning_set,
|
|
symbol_canonicalizer=symbol_canonicalizer)
|
|
ctx.emit('build', generate_unparser=generate_unparser,
|
|
unparse_script=(UnparseScript(unparse_script)
|
|
if unparse_script else None))
|
|
# ... and tell about how it went
|
|
except DiagnosticError:
|
|
# If there is a diagnostic error, don't say anything, the diagnostics
|
|
# are enough.
|
|
return None
|
|
else:
|
|
print('Code generation was successful')
|
|
return ctx
|
|
finally:
|
|
if lexer is not None:
|
|
lexer._dfa_code = None
|
|
langkit.reset()
|
|
|
|
|
|
def build(grammar=None, lexer=None, lkt_file=None,
|
|
warning_set=default_warning_set, mains=False):
|
|
"""
|
|
Shortcut for `build_and_run` to only build.
|
|
"""
|
|
build_and_run(grammar=grammar, lexer=lexer, lkt_file=lkt_file,
|
|
warning_set=warning_set)
|
|
|
|
|
|
def build_and_run(grammar=None, py_script=None, ada_main=None, lexer=None,
|
|
lkt_file=None, types_from_lkt=False,
|
|
lkt_semantic_checks=False, ocaml_main=None,
|
|
warning_set=default_warning_set, generate_unparser=False,
|
|
symbol_canonicalizer=None, mains=False,
|
|
show_property_logging=False, unparse_script=unparse_script):
|
|
"""
|
|
Compile and emit code for `ctx` and build the generated library. Then,
|
|
execute the provided scripts/programs, if any.
|
|
|
|
An exception is raised if any step fails (the script must return code 0).
|
|
|
|
:param langkit.lexer.Lexer lexer: The lexer to use along with the grammar.
|
|
See emit_and_print_errors.
|
|
|
|
:param str|None lkt_file: If provided, file from which to read the Lkt
|
|
language spec.
|
|
|
|
:param bool types_from_lkt: If true (valid only when `lkt_file` is not
|
|
None), first unparse the DSL and then do the build based on node
|
|
definitions from the unparsing result. False by default.
|
|
|
|
:param None|str py_script: If not None, name of the Python script to run
|
|
with the built library available.
|
|
|
|
:param None|str|list[str] ada_main: If not None, list of name of main
|
|
source files for Ada programs to build and run with the generated
|
|
library. If the input is a single string, consider it's a single mail
|
|
source file.
|
|
|
|
:param None|str ocaml_main: If not None, name of the OCaml source file to
|
|
build and run with the built library available.
|
|
|
|
:param WarningSet warning_set: Set of warnings to emit.
|
|
|
|
:param bool generate_unparser: Whether to generate unparser.
|
|
|
|
:param langkit.compile_context.LibraryEntity|None symbol_canonicalizer:
|
|
Symbol canonicalizer to use for this context, if any.
|
|
|
|
:param bool mains: Whether to build mains.
|
|
|
|
:param bool show_property_logging: If true, any property that has been
|
|
marked with tracing activated will be traced on stdout by default,
|
|
without need for any config file.
|
|
|
|
:param None|str unparse_script: Script to unparse the language spec.
|
|
"""
|
|
assert not types_from_lkt or lkt_file is not None
|
|
|
|
class Manage(ManageScript):
|
|
def __init__(self, ctx):
|
|
self._cached_context = ctx
|
|
super().__init__(root_dir=os.getcwd())
|
|
|
|
def create_context(self, args):
|
|
return self._cached_context
|
|
|
|
build_mode = 'dev'
|
|
|
|
def manage_run(generate_only, types_from_lkt, additional_args):
|
|
ctx = prepare_context(grammar, lexer, lkt_file, warning_set,
|
|
symbol_canonicalizer=symbol_canonicalizer,
|
|
show_property_logging=show_property_logging,
|
|
types_from_lkt=types_from_lkt,
|
|
lkt_semantic_checks=lkt_semantic_checks)
|
|
|
|
m = Manage(ctx)
|
|
|
|
extensions_dir = P.abspath('extensions')
|
|
if P.isdir(extensions_dir):
|
|
ctx.extensions_dir = extensions_dir
|
|
|
|
# First build the library. Forward all test.py's arguments to the
|
|
# libmanage call so that manual testcase runs can pass "-g", for
|
|
# instance.
|
|
argv = sys.argv[1:] + ['--full-error-traces', '-vnone']
|
|
|
|
# Generate the public Ada API only when necessary (i.e. if we have
|
|
# mains that do use this API). This reduces the time it takes to run
|
|
# tests.
|
|
if not mains and not ada_main:
|
|
argv.append('--no-ada-api')
|
|
|
|
argv.append('make')
|
|
|
|
argv.append('--build-mode={}'.format(build_mode))
|
|
for w in WarningSet.available_warnings:
|
|
argv.append(
|
|
'-{}{}'.format('W' if w in warning_set else 'w', w.name)
|
|
)
|
|
if not pretty_print:
|
|
argv.append('--no-pretty-print')
|
|
if generate_unparser:
|
|
argv.append('--generate-unparser')
|
|
|
|
# For testsuite performance, do not generate mains unless told
|
|
# otherwise.
|
|
if not mains:
|
|
argv.append('--disable-all-mains')
|
|
|
|
argv.extend(additional_args)
|
|
m.run(argv)
|
|
|
|
# Flush stdout and stderr, so that diagnostics appear deterministically
|
|
# before the script/program output.
|
|
sys.stdout.flush()
|
|
sys.stderr.flush()
|
|
|
|
return ctx, m
|
|
|
|
unparse_args = (['--unparse-script', unparse_script]
|
|
if unparse_script else [])
|
|
|
|
if unparse_script and types_from_lkt:
|
|
# RA22-015: Unparse the language to concrete syntax, then use the
|
|
# result to do a full build. Note that we don't unparse the DSL during
|
|
# the second run, as dsl_unparse requires Python sources, which the
|
|
# second run does not have access to.
|
|
manage_run(generate_only=True,
|
|
types_from_lkt=False,
|
|
additional_args=unparse_args)
|
|
langkit.reset()
|
|
ctx, m = manage_run(generate_only=False,
|
|
types_from_lkt=True,
|
|
additional_args=[])
|
|
else:
|
|
ctx, m = manage_run(generate_only=False,
|
|
types_from_lkt=False,
|
|
additional_args=unparse_args)
|
|
|
|
# Write a "setenv" script to make developper investigation convenient
|
|
with open('setenv.sh', 'w') as f:
|
|
m.write_setenv(build_mode, f)
|
|
|
|
env = m.derived_env(build_mode)
|
|
|
|
def run(*argv, **kwargs):
|
|
valgrind = kwargs.pop('valgrind', False)
|
|
suppressions = kwargs.pop('valgrind_suppressions', [])
|
|
assert not kwargs
|
|
|
|
if valgrind_enabled and valgrind:
|
|
argv = valgrind_cmd(list(argv), suppressions)
|
|
|
|
subprocess.check_call(argv, env=env)
|
|
|
|
if py_script is not None:
|
|
# Run the Python script. Note that in order to use the generated
|
|
# library, we have to use the special Python interpreter the testsuite
|
|
# provides us. See the corresponding code in
|
|
# testuite_support/python_driver.py.
|
|
python_interpreter = os.environ['PYTHON_INTERPRETER']
|
|
run(python_interpreter, py_script)
|
|
|
|
if ada_main is not None:
|
|
if isinstance(ada_main, str):
|
|
ada_main = [ada_main]
|
|
|
|
# Generate a project file to build the given Ada main and then run
|
|
# the program. Do a static build to improve the debugging experience.
|
|
with open('gen.gpr', 'w') as f:
|
|
f.write(project_template.format(
|
|
main_sources=', '.join('"{}"'.format(m) for m in ada_main)
|
|
))
|
|
run('gprbuild', '-Pgen', '-q', '-p',
|
|
'-XLIBRARY_TYPE=static',
|
|
'-XXMLADA_BUILD=static')
|
|
|
|
for i, m in enumerate(ada_main):
|
|
assert m.endswith('.adb')
|
|
if i > 0:
|
|
print('')
|
|
if len(ada_main) > 1:
|
|
print('== {} =='.format(m))
|
|
sys.stdout.flush()
|
|
run(P.join('obj', m[:-4]), valgrind=True)
|
|
|
|
if ocaml_main is not None:
|
|
# Set up a Dune project
|
|
with open('dune', 'w') as f:
|
|
f.write("""
|
|
(executable
|
|
(name {})
|
|
(flags (-w -9))
|
|
(libraries {}))
|
|
""".format(ocaml_main, ctx.c_api_settings.lib_name))
|
|
with open('dune-project', 'w') as f:
|
|
f.write('(lang dune 1.6)')
|
|
|
|
# Build the ocaml executable
|
|
run('dune', 'build', '--display', 'quiet', '--root', '.',
|
|
'./{}.exe'.format(ocaml_main))
|
|
|
|
# Run the ocaml executable
|
|
run('./_build/default/{}.exe'.format(ocaml_main),
|
|
valgrind=True,
|
|
valgrind_suppressions=['ocaml'])
|