mirror of
https://github.com/AdaCore/learn.git
synced 2026-02-12 13:00:42 -08:00
539 lines
18 KiB
Python
Executable File
539 lines
18 KiB
Python
Executable File
#! /usr/bin/env python3
|
|
|
|
"""
|
|
This program will try to compile and execute code blocks.
|
|
The default behavior is to:
|
|
- If the user indicated that the example should be ran (more on that later):
|
|
a. Run gnatmake on the unit named 'main' if there are several, or on the
|
|
first and only one if there is only one
|
|
b. Run the resulting program and check the return code
|
|
- Else:
|
|
a. Run gcc on every Ada file
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import subprocess as S
|
|
from os import path as P
|
|
import glob
|
|
import re
|
|
|
|
import blocks
|
|
import checks
|
|
import fmt_utils
|
|
import toolchain_setup
|
|
|
|
LOOK_FOR_PREVIOUS_CHECKS = True
|
|
|
|
verbose = False
|
|
all_diagnostics = False
|
|
max_columns = 0 # no check for max. columns
|
|
force_checks = False
|
|
|
|
|
|
class Diag(object):
|
|
def __init__(self, file, line, col, msg):
|
|
self.file = file
|
|
self.line = line
|
|
self.col = col
|
|
self.msg = msg
|
|
|
|
def __repr__(self):
|
|
return "{}:{}:{}: {}".format(self.file, self.line, self.col, self.msg)
|
|
|
|
|
|
def check_block(block : blocks.CodeBlock,
|
|
json_file : str,
|
|
verbose : bool = verbose,
|
|
all_diagnostics : bool = all_diagnostics,
|
|
max_columns : int = max_columns,
|
|
force_checks : bool = force_checks):
|
|
|
|
def run(*run_args):
|
|
if verbose:
|
|
print("Running \"{}\"".format(" ".join(run_args)))
|
|
try:
|
|
output = S.check_output(run_args, stderr=S.STDOUT).decode("utf-8")
|
|
all_output.extend(output.splitlines())
|
|
except S.CalledProcessError as e:
|
|
all_output.extend(e.output.decode("utf-8").splitlines())
|
|
raise e
|
|
|
|
return output
|
|
|
|
def set_versions():
|
|
gcc_version = None
|
|
gnat_version = None
|
|
gnat_prove_version = None
|
|
gprbuild_version = None
|
|
try:
|
|
gcc_version = run("gcc", "--version").partition('\n')[0]
|
|
gnat_version = run("gnat", "--version").partition('\n')[0]
|
|
gnat_prove_version = run("gnatprove", "--version").partition('\n')[0]
|
|
gprbuild_version = run("gprbuild", "--version").partition('\n')[0]
|
|
except:
|
|
gcc_version = "<unknown>" if gcc_version is None else gcc_version
|
|
gnat_version = "<unknown>" if gnat_version is None else gnat_version
|
|
gnat_prove_version = "<unknown>" if gnat_prove_version is None else gnat_prove_version
|
|
gprbuild_version = "<unknown>" if gprbuild_version is None else gprbuild_version
|
|
|
|
return gcc_version, gnat_version, gnat_prove_version, gprbuild_version
|
|
|
|
def extract_diagnostics(lines):
|
|
diags = []
|
|
r = re.compile("(.+?):(\d+):(\d+): (.+)")
|
|
for l in lines:
|
|
m = r.match(l)
|
|
if m:
|
|
f, l, c, t = m.groups()
|
|
diags.append(Diag(f, int(l), int(c), t))
|
|
return diags
|
|
|
|
def remove_string(some_text, rem):
|
|
return re.sub(".*" + rem + ".*\n?","", some_text)
|
|
|
|
has_error = False
|
|
loc = "at {}:{} (code block hash: {})".format(
|
|
block.rst_file, block.line_start, block.text_hash_short)
|
|
|
|
all_output = []
|
|
|
|
def print_diags():
|
|
diags = extract_diagnostics(all_output)
|
|
for diag in diags:
|
|
diag.line = diag.line + block.line_start
|
|
diag.file = block.rst_file
|
|
print(diag)
|
|
|
|
def print_error(*error_args):
|
|
fmt_utils.error(*error_args)
|
|
print_diags()
|
|
|
|
def cleanup_project(language, project_filename, main_file):
|
|
#
|
|
# Clean-up source-code examples after compilation
|
|
#
|
|
if project_filename is not None:
|
|
|
|
try:
|
|
run("gprclean", "-P", project_filename)
|
|
|
|
run("gnatprove", "-P", project_filename, "--clean")
|
|
except S.CalledProcessError as e:
|
|
out = str(e.output.decode("utf-8"))
|
|
|
|
if language == "c":
|
|
try:
|
|
cmd = ["rm", "-f"] + glob.glob('*.o') + glob.glob('*.gch')
|
|
if main_file is not None:
|
|
cmd.append(P.splitext(main_file)[0])
|
|
out = run(*cmd)
|
|
except S.CalledProcessError as e:
|
|
print_error(loc, "Failed to clean-up example")
|
|
print(e.output)
|
|
has_error = True
|
|
|
|
toolchain_setup.set_toolchain(block)
|
|
|
|
project_block_dir = os.path.dirname(json_file)
|
|
os.chdir(project_block_dir)
|
|
|
|
if block.no_check:
|
|
if verbose:
|
|
print("Skipping code block {}".format(loc))
|
|
return has_error
|
|
|
|
if LOOK_FOR_PREVIOUS_CHECKS:
|
|
ref_block_check = None
|
|
|
|
try:
|
|
ref_block_check = checks.BlockCheck.from_json_file()
|
|
except:
|
|
pass
|
|
|
|
if ref_block_check is not None and not force_checks:
|
|
has_error = not ref_block_check.status_ok
|
|
if verbose:
|
|
print("Code block {} already checked. Skipping...".format(loc))
|
|
if __name__ == '__main__':
|
|
print("WARNING: Code block {} already checked: use '--force' to re-run the check. Skipping...".format(loc))
|
|
if has_error:
|
|
print_error(
|
|
loc, "Previous check of example has failed"
|
|
)
|
|
return has_error
|
|
|
|
if verbose:
|
|
print(fmt_utils.header("Checking code block {}".format(loc)))
|
|
|
|
gcc_version, gnat_version, gnat_prove_version, gprbuild_version = set_versions()
|
|
|
|
if verbose:
|
|
import shutil
|
|
print("GCC version {}".format(gcc_version))
|
|
print("GNAT version {}".format(gnat_version))
|
|
print("GNATprove version {}".format(gnat_prove_version))
|
|
print("GPRbuild version {}".format(gprbuild_version))
|
|
|
|
print("GCC: {}".format(shutil.which("gcc")))
|
|
print("GNAT: {}".format(shutil.which("gnat")))
|
|
print("GNATprove: {}".format(shutil.which("gnatprove")))
|
|
print("GPRbuild: {}".format(shutil.which("gprbuild")))
|
|
|
|
block_check = checks.BlockCheck(text_hash=block.text_hash, text_hash_short=block.text_hash_short)
|
|
block_check.status_ok = True
|
|
|
|
# Syntax check
|
|
if 'nosyntax-check' not in block.classes:
|
|
check_error = False
|
|
|
|
for source_file in block.source_files:
|
|
|
|
try:
|
|
if block.language == "ada":
|
|
commands = ["gcc", "-c", "-gnats", "-gnatyg0-s"]
|
|
if max_columns > 0:
|
|
commands.append("-gnatyM" + str(max_columns))
|
|
out = run(*commands +
|
|
block.compiler_switches +
|
|
[source_file])
|
|
elif block.language == "c":
|
|
out = run("gcc", "-c", source_file)
|
|
|
|
if out:
|
|
print_error(loc, "Failed to syntax check example")
|
|
check_error = True
|
|
except S.CalledProcessError:
|
|
print_error(loc, "Failed to syntax check example")
|
|
check_error = True
|
|
|
|
code_check = checks.CodeCheck(version=gcc_version,
|
|
status_ok=(not check_error))
|
|
block_check.add_check("SYNTAX", code_check)
|
|
|
|
if check_error:
|
|
has_error = True
|
|
|
|
if block.syntax_only:
|
|
cleanup_project(block.language,
|
|
block.project_filename,
|
|
block.project_main_file)
|
|
block_check.status_ok = not has_error
|
|
block_check.to_json_file()
|
|
return has_error
|
|
|
|
compile_error = False
|
|
prove_error = False
|
|
is_prove_error_class = False
|
|
|
|
if block.compile_it:
|
|
check_error = False
|
|
|
|
if block.language == "ada":
|
|
cmdline = ["gprclean", "-P", block.project_filename]
|
|
try:
|
|
run(*cmdline)
|
|
out = run("gprbuild", "-q", "-P", block.project_filename)
|
|
except S.CalledProcessError as e:
|
|
if 'ada-expect-compile-error' in block.classes:
|
|
compile_error = True
|
|
else:
|
|
print_error(loc, "Failed to compile example")
|
|
print(e.output)
|
|
check_error = True
|
|
out = str(e.output.decode("utf-8"))
|
|
|
|
out = remove_string(out, "using project")
|
|
with open("build.log", u"w") as logfile:
|
|
logfile.write(out)
|
|
|
|
code_check = checks.CodeCheck(version=gnat_version,
|
|
status_ok=(not check_error),
|
|
logfile="build.log",
|
|
cmdline=str(cmdline))
|
|
|
|
block_check.add_check("BUILD", code_check)
|
|
|
|
if check_error:
|
|
has_error = True
|
|
|
|
elif block.language == "c":
|
|
cmdline = None
|
|
try:
|
|
cmdline = ["gcc", "-o",
|
|
P.splitext(block.project_main_file)[0]] + glob.glob('*.c')
|
|
out = run(*cmdline)
|
|
except S.CalledProcessError as e:
|
|
if 'c-expect-compile-error' in block.classes:
|
|
compile_error = True
|
|
else:
|
|
print_error(loc, "Failed to compile example")
|
|
print(e.output)
|
|
check_error = True
|
|
out = str(e.output.decode("utf-8"))
|
|
with open("build.log", u"w") as logfile:
|
|
logfile.write(out)
|
|
|
|
code_check = checks.CodeCheck(version=gcc_version,
|
|
status_ok=(not check_error),
|
|
logfile="build.log",
|
|
cmdline=str(cmdline))
|
|
|
|
block_check.add_check("BUILD", code_check)
|
|
|
|
if check_error:
|
|
has_error = True
|
|
|
|
if not compile_error and not has_error and block.run_it:
|
|
check_error = False
|
|
cmdline = None
|
|
|
|
if block.language == "ada":
|
|
try:
|
|
cmdline = ["./{}".format(P.splitext(block.project_main_file)[0])]
|
|
out = run(*cmdline)
|
|
|
|
if 'ada-run-expect-failure' in block.classes:
|
|
print_error(
|
|
loc, "Running of example should have failed"
|
|
)
|
|
check_error = True
|
|
|
|
except S.CalledProcessError as e:
|
|
if 'ada-run-expect-failure' in block.classes:
|
|
if verbose:
|
|
print("Running of example expectedly failed")
|
|
else:
|
|
print_error(loc, "Running of example failed")
|
|
check_error = True
|
|
|
|
out = str(e.output.decode("utf-8"))
|
|
|
|
with open("run.log", u"w") as logfile:
|
|
logfile.write(out)
|
|
|
|
elif block.language == "c":
|
|
try:
|
|
cmdline = ["./{}".format(P.splitext(block.project_main_file)[0])]
|
|
out = run(*cmdline)
|
|
|
|
if 'c-run-expect-failure' in block.classes:
|
|
print_error(
|
|
loc, "Running of example should have failed"
|
|
)
|
|
check_error = True
|
|
|
|
except S.CalledProcessError as e:
|
|
if 'c-run-expect-failure' in block.classes:
|
|
if verbose:
|
|
print("Running of example expectedly failed")
|
|
else:
|
|
print_error(loc, "Running of example failed")
|
|
check_error = True
|
|
out = str(e.output.decode("utf-8"))
|
|
|
|
with open("run.log", u"w") as logfile:
|
|
logfile.write(out)
|
|
|
|
code_check = checks.CodeCheck(status_ok=(not check_error),
|
|
logfile="run.log",
|
|
cmdline=str(cmdline))
|
|
|
|
block_check.add_check("RUN", code_check)
|
|
|
|
if check_error:
|
|
has_error = True
|
|
|
|
if False:
|
|
check_error = False
|
|
|
|
for source_file in block.source_files:
|
|
if block.language == "ada":
|
|
try:
|
|
out = run("gcc", "-c", "-gnatc", "-gnatyg0-s",
|
|
source_file)
|
|
except S.CalledProcessError as e:
|
|
if 'ada-expect-compile-error' in block.classes:
|
|
compile_error = True
|
|
else:
|
|
print_error(loc, "Failed to compile example")
|
|
check_error = True
|
|
out = str(e.output.decode("utf-8"))
|
|
|
|
with open("compile.log", u"w+") as logfile:
|
|
logfile.write(out)
|
|
|
|
elif block.language == "c":
|
|
try:
|
|
out = run("gcc", "-c", source_file)
|
|
except S.CalledProcessError as e:
|
|
if 'c-expect-compile-error' in block.classes:
|
|
compile_error = True
|
|
else:
|
|
print_error(loc, "Failed to compile example")
|
|
check_error = True
|
|
out = str(e.output.decode("utf-8"))
|
|
|
|
with open("compile.log", u"w+") as logfile:
|
|
logfile.write(out)
|
|
|
|
if check_error:
|
|
has_error = True
|
|
|
|
if block.prove_it:
|
|
check_error = False
|
|
|
|
if block.language == "ada":
|
|
|
|
is_prove_error_class = any(c in ['ada-expect-prove-error',
|
|
'ada-expect-compile-error',
|
|
'ada-run-expect-failure']
|
|
for c in block.classes)
|
|
extra_args = []
|
|
|
|
if 'prove_flow' in block.buttons \
|
|
or 'ada-prove-flow' in block.classes:
|
|
extra_args = ["--mode=flow"]
|
|
elif 'prove_flow_report_all' in block.buttons \
|
|
or 'ada-prove-flow-report-all' in block.classes:
|
|
extra_args = ["--mode=flow", "--report=all"]
|
|
elif 'prove_report_all' in block.buttons \
|
|
or 'ada-report-all' in block.classes:
|
|
extra_args = ["--report=all"]
|
|
|
|
line = ["gnatprove", "-P", block.spark_project_filename,
|
|
"--checks-as-errors", "--level=0",
|
|
"--no-axiom-guard", "--output=oneline"]
|
|
line.extend(extra_args)
|
|
|
|
try:
|
|
out = run(*line)
|
|
except S.CalledProcessError as e:
|
|
if is_prove_error_class:
|
|
prove_error = True
|
|
else:
|
|
print_error(loc, "Failed to prove example")
|
|
print(e.output)
|
|
check_error = True
|
|
out = str(e.output.decode("utf-8"))
|
|
|
|
out = remove_string(out, "Summary logged in")
|
|
with open("prove.log", u"w") as logfile:
|
|
logfile.write(out)
|
|
|
|
code_check = checks.CodeCheck(version=gnat_prove_version,
|
|
status_ok=(not check_error),
|
|
logfile="prove.log",
|
|
cmdline=str(line))
|
|
|
|
block_check.add_check("PROVE", code_check)
|
|
|
|
else:
|
|
print_error(loc, "Wrong language selected for prove button")
|
|
print(e.output)
|
|
check_error = True
|
|
|
|
code_check = checks.CodeCheck(status_ok=(not check_error))
|
|
block_check.add_check("PROVE", code_check)
|
|
|
|
if check_error:
|
|
has_error = True
|
|
|
|
|
|
if True:
|
|
check_error = False
|
|
|
|
if len(block.buttons) == 0:
|
|
print_error(loc, "Expected at least 'no_button' indicator, got none!")
|
|
check_error = True
|
|
|
|
if 'ada-expect-compile-error' in block.classes:
|
|
if not any(b in ['compile', 'run'] for b in block.buttons):
|
|
print_error(loc, "Expected compile or run button, got none!")
|
|
check_error = True
|
|
if not compile_error:
|
|
print_error(loc, "Expected compile error, got none!")
|
|
check_error = True
|
|
|
|
if 'ada-expect-prove-error' in block.classes:
|
|
if not block.prove_it:
|
|
print_error(loc, "Expected prove button, got none!")
|
|
check_error = True
|
|
|
|
if block.prove_it:
|
|
if is_prove_error_class and not prove_error:
|
|
print_error(loc, "Expected prove error, got none!")
|
|
check_error = True
|
|
|
|
if (any (c in ['ada-run-expect-failure','ada-norun'] for
|
|
c in block.classes)
|
|
and not 'run' in block.buttons):
|
|
print_error(loc, "Expected run button, got none!")
|
|
check_error = True
|
|
|
|
code_check = checks.CodeCheck(status_ok=(not check_error))
|
|
|
|
block_check.add_check("BUTTONS", code_check)
|
|
|
|
if check_error:
|
|
has_error = True
|
|
|
|
if not has_error and verbose:
|
|
fmt_utils.simple_success("SUCCESS")
|
|
|
|
cleanup_project(block.language,
|
|
block.project_filename,
|
|
block.project_main_file)
|
|
|
|
if all_diagnostics:
|
|
print_diags()
|
|
|
|
block_check.status_ok = not has_error
|
|
block_check.to_json_file()
|
|
|
|
toolchain_setup.reset_toolchain()
|
|
|
|
return has_error
|
|
|
|
|
|
def check_code_block_json(json_file):
|
|
|
|
b = blocks.CodeBlock.from_json_file(json_file)
|
|
|
|
if not b.active:
|
|
print("WARNING: Block is deactivated. Checking it nevertheless...")
|
|
|
|
has_error = check_block(b, json_file, verbose,
|
|
all_diagnostics, max_columns,
|
|
force_checks)
|
|
|
|
return has_error
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('json_files', type=str, nargs="+",
|
|
help="The JSON file for each code block")
|
|
parser.add_argument('--verbose', '-v', action='store_true',
|
|
help='Show more information')
|
|
parser.add_argument('--all-diagnostics', '-A', action='store_true')
|
|
parser.add_argument('--max-columns', type=int, default=0)
|
|
parser.add_argument('--force', '-f', action='store_true',
|
|
help="Force checks even if previous check exists.")
|
|
|
|
args = parser.parse_args()
|
|
|
|
verbose = args.verbose
|
|
all_diagnostics = args.all_diagnostics
|
|
max_columns = args.max_columns
|
|
force_checks = args.force
|
|
|
|
has_error = False
|
|
|
|
for f in args.json_files:
|
|
check_error = check_code_block_json(f)
|
|
if check_error:
|
|
has_error = True
|
|
|
|
exit(has_error)
|