#! /usr/bin/env python3 """ This program will extract every Ada code block in an Ada source file The default behavior is to: - Split the block with ``gnatchop`` """ import os import shutil import re import json from widget.chop import manual_chop, real_gnatchop import blocks import fmt_utils import toolchain_setup current_config = blocks.ConfigBlock( run_button=False, prove_button=True, accumulate_code=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) verbose = False code_block_at = None BASE_PROJECT_DIR = "projects" def get_project_dir(project): return BASE_PROJECT_DIR + "/" + project.replace(".", "/") COMMON_ADC = """ -- pragma Restrictions (No_Specification_of_Aspect => Import); -- pragma Restrictions (No_Use_Of_Pragma => Import); -- pragma Restrictions (No_Use_Of_Pragma => Interface); -- pragma Restrictions (No_Use_Of_Pragma => Linker_Options); -- pragma Restrictions (No_Dependence => System.Machine_Code); -- pragma Restrictions (No_Dependence => Machine_Code); """ SPARK_ADC = """ pragma Profile(GNAT_Extended_Ravenscar); pragma Partition_Elaboration_Policy(Sequential); pragma SPARK_Mode (On); pragma Warnings (Off, "no Global contract available"); pragma Warnings (Off, "subprogram * has no effect"); pragma Warnings (Off, "file name does not match"); """ MAIN_GPR=""" project Main is --MAIN_PLACEHOLDER-- package Compiler is for Default_Switches ("Ada") use ("-g", "-O0"); --COMPILER_SWITCHES_PLACEHOLDER-- end Compiler; package Builder is for Default_Switches ("Ada") use ("-g"); for Global_Configuration_Pragmas use "main.adc"; end Builder; end Main; """ MAIN_SPARK_GPR=""" project Main_Spark is --MAIN_PLACEHOLDER-- package Compiler is for Default_Switches ("Ada") use ("-g", "-O0"); --COMPILER_SWITCHES_PLACEHOLDER-- end Compiler; package Builder is for Default_Switches ("Ada") use ("-g"); for Global_Configuration_Pragmas use "main_spark.adc"; end Builder; end Main_Spark; """ def write_project_file(main_file, compiler_switches, spark_mode): gpr_filename = "main.gpr" adc_filename = "main.adc" main_gpr = MAIN_GPR if spark_mode: gpr_filename = "main_spark.gpr" adc_filename = "main_spark.adc" main_gpr = MAIN_SPARK_GPR adc_content = COMMON_ADC if spark_mode: adc_content += '\n' + SPARK_ADC with open(gpr_filename, u"w") as gpr_file: filtered_switches = [] for switch in compiler_switches: filtered_switches.append('"' + switch + '"') if filtered_switches: placeholder_str = "--COMPILER_SWITCHES_PLACEHOLDER--" switches_str = ', '.join(filtered_switches) line_str = f'for Switches ("Ada") use ({switches_str});' main_gpr = main_gpr.replace(placeholder_str, line_str) if main_file is not None: mains = [main_file] main_list = [f'"{x}"' for x in mains] to_insert = f"for Main use ({', '.join(main_list)});" else: to_insert = f"" main_gpr = main_gpr.replace("--MAIN_PLACEHOLDER--", to_insert) gpr_file.write(main_gpr) with open(adc_filename, u"w") as adc_file: adc_file.write(adc_content) return gpr_filename class ProjectsList(object): @staticmethod def from_json_file(json_filename): if os.path.isfile(json_filename): with open(json_filename, u'r') as f: projects_info = json.load(f) return ProjectsList(**projects_info) return None def __init__(self, projects=None): self.projects = projects if projects is not None else \ dict() def to_json_file(self, json_filename): projects_info = vars(self) with open(json_filename, u'w') as f: json.dump(projects_info, f, indent=4) def add(self, project): self.projects[project] = True def analyze_file(rst_file, extracted_projects_list_file=None): analysis_error = False with open(rst_file) as f: content = f.read() all_blocks = list(enumerate(filter( lambda b: b.language in ["ada", "c"] if isinstance(b, blocks.CodeBlock) else True, blocks.Block.get_blocks_from_rst(rst_file, content) ))) code_blocks = [(i, b) for i, b in all_blocks if isinstance(b, blocks.CodeBlock)] if code_block_at: for i, block in code_blocks: block.active = False if block.line_start < code_block_at < block.line_end: block.active = True def remove_string(some_text, rem): return re.sub(".*" + rem + ".*\n?","", some_text) projects = dict() extr_prjs = None if extracted_projects_list_file is not None: if os.path.exists(extracted_projects_list_file): extr_prjs = ProjectsList.from_json_file(extracted_projects_list_file) if verbose: print ("Extracted list of projects from existing JSON file.") else: extr_prjs = ProjectsList() if verbose: print ("JSON file with list of projects will be created.") for (i, b) in code_blocks: if not b.active: continue if b.project is None: print ("Error: project not set in {} at line {}".format( rst_file, str(b.line_start))) exit(1) if not b.project in projects: projects[b.project] = list() projects[b.project].append((i, b)) work_dir = os.getcwd() for project in projects: if extr_prjs is not None: extr_prjs.add(project) latest_project_dir = "latest" def init_project_dir(project): project_dir = get_project_dir(project) if os.path.exists(project_dir): if verbose: print("Project directory already exists: " + project_dir) print("Removing 'latest'...") shutil.rmtree(project_dir + "/" + latest_project_dir, ignore_errors=True) os.makedirs(project_dir, exist_ok=True) return project_dir project_dir = init_project_dir(project) if verbose: print(fmt_utils.header("Checking project {}".format(project))) print("Number of code blocks: {}".format(len(projects[project]))) for i, block in projects[project]: if isinstance(block, blocks.ConfigBlock): current_config.update(block) toolchain_setup.reset_toolchain() continue toolchain_setup.set_toolchain(block) os.chdir(work_dir) # change to work directory using absolute path has_error = False loc = "at {}:{} (code block #{})".format( rst_file, block.line_start, i) def print_error(*error_args): fmt_utils.error(*error_args) def chdir_project(): # combining path to work directory (absolute path) # and current project directory os.chdir(work_dir + "/" + project_dir) def update_latest(): def expand_source_files(): split = block.text.splitlines() source_files = list() if block.manual_chop: source_files = manual_chop(split) else: source_files = real_gnatchop(split, block.compiler_switches) if len(source_files) == 0: print_error(loc, "Failed to chop example, skipping\n") analysis_error = True raise for source_file in source_files: with open(source_file.basename, u"w") as code_file: code_file.write(source_file.content) return source_files chdir_project() os.makedirs(latest_project_dir, exist_ok=True) os.chdir(latest_project_dir) source_files = expand_source_files() chdir_project() return latest_project_dir, source_files def prepare_project_block_dir(latest_project_dir): project_block_dir = str(block.text_hash_short) ref_block = None copytree_latest = True if os.path.exists(project_block_dir): json_filename = "block_info.json" json_file = project_block_dir + "/" + json_filename if os.path.exists(json_file): copytree_latest = False ref_block = blocks.CodeBlock.from_json_file(json_file) else: print_error(loc, "Directory exists, but no JSON info file: removing it...\n") shutil.rmtree(project_block_dir, ignore_errors=True) if copytree_latest: # os.makedirs(project_block_dir) shutil.copytree(latest_project_dir, project_block_dir) return project_block_dir, ref_block try: latest_project_dir, source_files = update_latest() for source_file in source_files: block.source_files.append(source_file.basename) except Exception as e: print(e.message) print("Error while updating code for the block, continuing with next one!") block.to_json_file() toolchain_setup.reset_toolchain() continue project_block_dir, ref_block = prepare_project_block_dir(latest_project_dir) os.chdir(project_block_dir) if block.no_check: if verbose: print("Skipping code block {}".format(loc)) block.to_json_file() toolchain_setup.reset_toolchain() continue if block.syntax_only: block.to_json_file() toolchain_setup.reset_toolchain() continue compile_error = False prove_error = False def get_main_filename(block): if block.main_file is not None: main_file = block.main_file else: main_file = block.source_files[-1] return main_file if block.compile_it: if block.run_it: block.project_main_file = get_main_filename(block) block.project_filename = write_project_file(block.project_main_file, block.compiler_switches, spark_mode=False) if block.prove_it: if block.language == "ada": if block.run_it: block.project_main_file = get_main_filename(block) block.spark_project_filename = write_project_file(block.project_main_file, block.compiler_switches, spark_mode=True) else: print_error(loc, "Wrong language selected for prove button") print(e.output) has_error = True if len(block.buttons) == 0: print_error(loc, "Expected at least 'no_button' indicator, got none!") has_error = True block.to_json_file() toolchain_setup.reset_toolchain() os.chdir(work_dir) if extr_prjs is not None: extr_prjs.to_json_file(extracted_projects_list_file) return analysis_error if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('rst_files', type=str, nargs="+", help="The rst file from which to extract doc") parser.add_argument('--build-dir', '-B', type=str, default=None, help='Dir in which to build code') parser.add_argument('--extracted_projects', type=str, default=None, help='JSON file containing list of extracted projects') parser.add_argument('--verbose', '-v', action='store_true', help='Show more information') parser.add_argument('--all-diagnostics', '-A', action='store_true') parser.add_argument('--code-block-at', type=int, default=0) parser.add_argument('--max-columns', type=int, default=0) args = parser.parse_args() args.rst_files = [os.path.abspath(f) for f in args.rst_files] verbose = args.verbose code_block_at = args.code_block_at extracted_projects = args.extracted_projects build_dir = args.build_dir if extracted_projects is None and \ build_dir is None: print("ERROR: at least --extracted_projects or --build-dir should be specified (or both).") exit(1) if extracted_projects: extracted_projects = os.path.abspath(extracted_projects) if build_dir is None: build_dir = os.path.dirname(extracted_projects) if build_dir == '': ## Special case: no directory in path build_dir = os.getcwd() if verbose: print("Build directory is set to: " + build_dir) if not os.path.exists(build_dir): os.makedirs(build_dir) test_error = False os.chdir(build_dir) for f in args.rst_files: analysis_error = analyze_file(f, extracted_projects) if analysis_error: test_error = True if test_error: fmt_utils.simple_success("TEST ERROR") exit(1) elif verbose: fmt_utils.simple_success("TEST SUCCESS")