diff --git a/lkql_checker/src/gnatcheck-compiler.adb b/lkql_checker/src/gnatcheck-compiler.adb index 002f1f860..ee5030219 100644 --- a/lkql_checker/src/gnatcheck-compiler.adb +++ b/lkql_checker/src/gnatcheck-compiler.adb @@ -1351,6 +1351,50 @@ package body Gnatcheck.Compiler is end if; end Restriction_Rule_Parameter; + ---------------------------- + -- Has_Access_To_Codepeer -- + ---------------------------- + + function Has_Access_To_Codepeer return Boolean + is + Gnatls : String_Access := Locate_Exec_On_Path ("codepeer-gnatls"); + Res : Boolean := False; + begin + if Gnatls /= null then + Res := True; + Free (Gnatls); + end if; + return Res; + end Has_Access_To_Codepeer; + + ------------------- + -- GPRbuild_Exec -- + ------------------- + + function GPRbuild_Exec return String is + begin + if Has_Access_To_Codepeer then + return "codepeer-gprbuild"; + else + return "gprbuild"; + end if; + end GPRbuild_Exec; + + ---------------- + -- Gnatls_Exec -- + ---------------- + + function Gnatls_Exec return String is + begin + if Has_Access_To_Codepeer then + return "codepeer-gnatls"; + elsif Target.all /= "" then + return Target.all & "-gnatls"; + else + return "gnatls"; + end if; + end Gnatls_Exec; + ------------------------- -- Set_Compiler_Checks -- ------------------------- @@ -1448,6 +1492,9 @@ package body Gnatcheck.Compiler is if Target.all /= "" then Num_Args := @ + 1; Args (Num_Args) := new String'("--target=" & Target.all); + elsif Has_Access_To_Codepeer then + Num_Args := @ + 1; + Args (Num_Args) := new String'("--target=codepeer"); end if; else -- Target and runtime will be taken from config project anyway @@ -1514,7 +1561,7 @@ package body Gnatcheck.Compiler is function Spawn_GPRbuild (Output_File : String) return Process_Id is Pid : Process_Id; - GPRbuild : String_Access := Locate_Exec_On_Path ("gprbuild"); + GPRbuild : String_Access := Locate_Exec_On_Path (GPRbuild_Exec); Prj : constant String := Gnatcheck_Prj.Source_Prj; Last_Source : constant SF_Id := Last_Argument_Source; Args : Argument_List (1 .. 128 + Integer (Last_Source)); @@ -1537,6 +1584,11 @@ package body Gnatcheck.Compiler is Args (8) := new String'("--restricted-to-languages=ada"); Num_Args := 8; + if Has_Access_To_Codepeer then + Num_Args := @ + 1; + Args (Num_Args) := new String'("--target=codepeer"); + end if; + if Process_Num > 1 then Num_Args := @ + 1; Args (Num_Args) := new String'("-j" & Image (Process_Num)); @@ -1581,7 +1633,7 @@ package body Gnatcheck.Compiler is Append_Variables (Args, Num_Args); if Debug_Mode then - Put ("gprbuild"); + Put (GPRbuild_Exec); for J in 1 .. Num_Args loop Put (" " & Args (J).all); diff --git a/lkql_checker/src/gnatcheck-compiler.ads b/lkql_checker/src/gnatcheck-compiler.ads index 947a7410e..ae6869d66 100644 --- a/lkql_checker/src/gnatcheck-compiler.ads +++ b/lkql_checker/src/gnatcheck-compiler.ads @@ -25,6 +25,21 @@ with GNAT.OS_Lib; use GNAT.OS_Lib; package Gnatcheck.Compiler is + --------------------- + -- Runtime helpers -- + --------------------- + + function Has_Access_To_Codepeer return Boolean; + -- Returns whether the current gnatcheck process can access to the codepeer + -- tools. This function tests if the `codepeer-gnatls` executable can be + -- accessed. + + function GPRbuild_Exec return String; + -- Return the executable name to use in order to spawn a GPRBuild process + + function Gnatls_Exec return String; + -- Return the executable name to use in order to spawn a GNATLS process + -------------------------------------------------------- -- Using in GNATCHECK checks performed by the compiler -- -------------------------------------------------------- diff --git a/lkql_checker/src/gnatcheck-projects.adb b/lkql_checker/src/gnatcheck-projects.adb index ab7ef44a5..b37596aaf 100644 --- a/lkql_checker/src/gnatcheck-projects.adb +++ b/lkql_checker/src/gnatcheck-projects.adb @@ -967,6 +967,17 @@ package body Gnatcheck.Projects is GPR2.Project.Registry.Pack.Check_Attributes (+"Check"); end Register_Tool_Attributes; + ------------------------ + -- Set_Default_Target -- + ------------------------ + + procedure Set_Default_Target is + begin + if not Gnatkp_Mode and then Has_Access_To_Codepeer then + GPR2.KB.Set_Default_Target ("codepeer"); + end if; + end Set_Default_Target; + ------------------------- -- Set_External_Values -- ------------------------- @@ -1769,11 +1780,7 @@ package body Gnatcheck.Projects is -- Target hasn't been set explicitly and codepeer-gnatls -- is available, force its use by setting the "codepeer" -- target. - - if Target'Length = 0 - and then Locate_Exec_On_Path ("codepeer-gnatls") /= null - then - Free (Target); + if Target'Length = 0 and then Has_Access_To_Codepeer then Target := new String'("codepeer"); end if; diff --git a/lkql_checker/src/gnatcheck-projects.ads b/lkql_checker/src/gnatcheck-projects.ads index c9b57ea41..02e3f949b 100644 --- a/lkql_checker/src/gnatcheck-projects.ads +++ b/lkql_checker/src/gnatcheck-projects.ads @@ -354,6 +354,10 @@ package Gnatcheck.Projects is -- General project file processing -- ------------------------------------- + procedure Set_Default_Target; + -- If codepeer is on PATH, replaces default target with "codepeer", + -- does nothing in gnatkp mode. + procedure Initialize_Environment; -- Initializes the environment for extracting the information from the -- project file. This includes setting the parameters specific for the diff --git a/lkql_checker/src/gnatcheck-source_table.adb b/lkql_checker/src/gnatcheck-source_table.adb index 7ec87a364..05ebcc8ca 100644 --- a/lkql_checker/src/gnatcheck-source_table.adb +++ b/lkql_checker/src/gnatcheck-source_table.adb @@ -40,6 +40,7 @@ with GPR2.Project.Tree; with GPR2.Project.View; with GPR2.Project.Source.Set; +with Gnatcheck.Compiler; use Gnatcheck.Compiler; with Gnatcheck.Diagnoses; use Gnatcheck.Diagnoses; with Gnatcheck.Ids; use Gnatcheck.Ids; with Gnatcheck.Output; use Gnatcheck.Output; @@ -1500,10 +1501,7 @@ package body Gnatcheck.Source_Table is ----------------------- procedure Add_Runtime_Files is - Gnatls : String_Access := - Locate_Exec_On_Path (if Target.all /= "" - then Target.all & "-gnatls" - else "gnatls"); + Gnatls : String_Access := Locate_Exec_On_Path (Gnatls_Exec); Verbose : aliased String := "-v"; Status : aliased Integer; diff --git a/lkql_checker/src/gnatcheck_main.adb b/lkql_checker/src/gnatcheck_main.adb index 46c440aa7..63886bc91 100644 --- a/lkql_checker/src/gnatcheck_main.adb +++ b/lkql_checker/src/gnatcheck_main.adb @@ -377,6 +377,8 @@ begin OS_Exit (E_Success); end if; + Gnatcheck.Projects.Set_Default_Target; + -- If we have the project file specified as a tool parameter, analyze it. Gnatcheck.Projects.Process_Project_File (Gnatcheck_Prj); diff --git a/lkql_jit/language/src/main/java/com/adacore/lkql_jit/LKQLContext.java b/lkql_jit/language/src/main/java/com/adacore/lkql_jit/LKQLContext.java index b1dff7afa..17b0065b6 100644 --- a/lkql_jit/language/src/main/java/com/adacore/lkql_jit/LKQLContext.java +++ b/lkql_jit/language/src/main/java/com/adacore/lkql_jit/LKQLContext.java @@ -69,6 +69,20 @@ public final class LKQLContext { /** The project manager for the ada project. */ private Libadalang.ProjectManager projectManager; + /** Event handler for the project manager. */ + private final Libadalang.EventHandler eventHandler = + Libadalang.EventHandler.create( + (ctx, name, from, found, not_found_is_error) -> { + if (!found && not_found_is_error) { + boolean isFatal = !this.keepGoingOnMissingFile(); + this.getDiagnosticEmitter().emitMissingFile(from, name, isFatal, this); + if (isFatal) { + this.env.getContext().closeExited(null, 1); + } + } + }, + null); + /** * The user-specified source files to analyze. If not explicitly specified, those will be the * source files of the root project. @@ -141,10 +155,16 @@ public final class LKQLContext { /** The project file to analyse. */ @CompilerDirectives.CompilationFinal private String projectFile = null; + @CompilerDirectives.CompilationFinal private String target = null; + + @CompilerDirectives.CompilationFinal private String runtime = null; + /** The project's scenario variables. */ @CompilerDirectives.CompilationFinal(dimensions = 1) private Libadalang.ScenarioVariable[] scenarioVars = null; + private Boolean useAutoProvider = null; + /** The ada files passed through the command line. */ @CompilerDirectives.CompilationFinal(dimensions = 1) private String[] files = null; @@ -195,6 +215,7 @@ public LKQLContext(TruffleLanguage.Env env, GlobalScope global) { public void finalizeContext() { this.adaContext.close(); if (this.projectManager != null) this.projectManager.close(); + this.eventHandler.close(); } // ----- Getters ----- @@ -273,11 +294,17 @@ public String getProjectFile() { } public String getTarget() { - return this.env.getOptions().get(LKQLLanguage.target); + if (this.target == null) { + this.target = this.env.getOptions().get(LKQLLanguage.target); + } + return this.target; } public String getRuntime() { - return this.env.getOptions().get(LKQLLanguage.runtime); + if (this.runtime == null) { + this.runtime = this.env.getOptions().get(LKQLLanguage.runtime); + } + return this.runtime; } /** Return the list of scenario variables to specify when loading the GPR project file. */ @@ -310,6 +337,14 @@ public Libadalang.ScenarioVariable[] getScenarioVars() { return this.scenarioVars; } + /** Get whether to use the Libadalang's auto provider. */ + public boolean useAutoProvider() { + if (this.useAutoProvider == null) { + this.useAutoProvider = this.env.getOptions().get(LKQLLanguage.useAutoProvider); + } + return this.useAutoProvider; + } + /** * Get the files to analyse. * @@ -490,6 +525,11 @@ public CheckerUtils.DiagnosticEmitter getDiagnosticEmitter() { // ----- Project analysis methods ----- + /** Returns the executable name to call to spawn a gnatls process */ + public String getGnatLs() { + return this.getTarget().isEmpty() ? "gnatls" : this.getTarget() + "-gnatls"; + } + /** Parse the ada source files and store analysis units and root nodes. */ @CompilerDirectives.TruffleBoundary public void parseSources() { @@ -531,9 +571,10 @@ public void parseSources() { /** Initialize the ada sources. */ public void initSources() { - // Prepare the list of ada files to analyse + // Reset the context fields this.specifiedSourceFiles.clear(); this.allSourceFiles.clear(); + this.parsed = false; // Add all the user-specified files to process after verifying they exist for (String file : this.getFiles()) { @@ -548,31 +589,9 @@ public void initSources() { } } - // Setup the event handler - final Libadalang.EventHandler.UnitRequestedCallback unitRequested = - (ctx, name, from, found, not_found_is_error) -> { - if (!found && not_found_is_error) { - boolean isFatal = !this.keepGoingOnMissingFile(); - this.getDiagnosticEmitter().emitMissingFile(from, name, isFatal, this); - if (isFatal) { - this.env.getContext().closeExited(null, 1); - } - } - }; - final Libadalang.EventHandler eventHandler = - Libadalang.EventHandler.create(unitRequested, null); - - // If the option is the empty string, the language implementation will end up setting it to - // the - // default - // value for its language (e.g. iso-8859-1 for Ada). - String charset = this.env.getOptions().get(LKQLLanguage.charset); - - // Get the project file and parse it if there is one + // Get the project file and use it if there is one String projectFileName = this.getProjectFile(); - if (projectFileName != null && !projectFileName.isEmpty() && !projectFileName.isBlank()) { - // Create the project manager this.projectManager = Libadalang.ProjectManager.create( projectFileName, @@ -580,78 +599,86 @@ public void initSources() { this.getTarget(), this.getRuntime()); - // Test if there is any diagnostic in the project manager + // Forward the project diagnostics if there are some if (!this.projectManager.getDiagnostics().isEmpty()) { throw LKQLRuntimeException.fromMessage( "Error(s) during project opening: " + this.projectManager.getDiagnostics()); } + // Get the subproject provided by the user final String subprojectName = this.env.getOptions().get(LKQLLanguage.subprojectFile); final String[] subprojects = subprojectName.isEmpty() ? null : new String[] {subprojectName}; // If no files were specified by the user, the files to analyze are those of the root - // project - // (i.e. without recusing into project dependencies) + // project (i.e. without recursing into project dependencies) if (this.specifiedSourceFiles.isEmpty()) { - this.specifiedSourceFiles = + this.specifiedSourceFiles.addAll( Arrays.stream( this.projectManager.getFiles( Libadalang.SourceFileMode.ROOT_PROJECT, subprojects)) - .toList(); + .toList()); } // The `units()` built-in function must return all units of the project including units - // from - // its - // dependencies. So let's retrieve all those files as well. - this.allSourceFiles = + // from its dependencies. So let's retrieve all those files as well. + this.allSourceFiles.addAll( Arrays.stream( this.projectManager.getFiles( Libadalang.SourceFileMode.WHOLE_PROJECT, subprojects)) - .toList(); + .toList()); this.adaContext = this.projectManager.createContext( subprojectName.isEmpty() ? null : subprojectName, - eventHandler, + this.eventHandler, true, 8); - } else { - // When no project is specified, `units()` should return the same set of units as - // `specified_units()`. - this.allSourceFiles = this.specifiedSourceFiles; + } - // If required, create an auto provider with the specified files. + // Else, either load the implicit project or the Libadalang's auto-provider if required + // by the user. + else { + // We should not get any scenario variable if we are being run without a project file. + if (this.getScenarioVars().length != 0) { + throw LKQLRuntimeException.fromMessage( + "Scenario variable specifications require a project file"); + } + + // If the option is the empty string, the language implementation will end up setting it + // to + // the default value for its language (e.g. iso-8859-1 for Ada). + String charset = this.env.getOptions().get(LKQLLanguage.charset); + + // Create the required unit provider to initialize the analysis context final Libadalang.UnitProvider provider; - if (this.env.getOptions().get(LKQLLanguage.useAutoProvider)) { + if (this.useAutoProvider()) { + this.allSourceFiles.addAll(this.specifiedSourceFiles); final List allFiles = this.fetchAdaRuntimeFiles(); allFiles.addAll(this.allSourceFiles); provider = Libadalang.createAutoProvider(allFiles.toArray(new String[0]), charset); } else { - provider = null; - } - - // We should not get any scenario variable if we are being run without a project file. - if (this.getScenarioVars().length != 0) { - throw LKQLRuntimeException.fromMessage( - "Scenario variable specifications require a project file"); + this.projectManager = + Libadalang.ProjectManager.createImplicit( + this.getTarget(), this.getRuntime()); + this.allSourceFiles.addAll( + Arrays.stream( + this.projectManager.getFiles( + Libadalang.SourceFileMode.WHOLE_PROJECT)) + .toList()); + provider = this.projectManager.getProvider(); } + // Create the ada context and store it in the LKQL context this.adaContext = Libadalang.AnalysisContext.create( - charset, null, provider, eventHandler, true, 8); + charset, null, provider, this.eventHandler, true, 8); // In the absence of a project file, we consider for now that there are no configuration // pragmas. this.adaContext.setConfigPragmasMapping(null, null); } - - eventHandler.close(); - - // The retrieved source files are not yet parsed - this.parsed = false; } /** @@ -664,7 +691,15 @@ public void initSources() { public List fetchAdaRuntimeFiles() { final List runtimeFiles = new ArrayList<>(); try { - final Process gnatls = new ProcessBuilder("gnatls", "-v").start(); + final Process gnatls = + new ProcessBuilder( + this.getGnatLs(), + "-v", + this.getTarget().isEmpty() + ? "" + : "--target=" + this.getTarget(), + this.getRuntime().isEmpty() ? "" : "--RTS=" + this.getRuntime()) + .start(); final BufferedReader reader = new BufferedReader(new InputStreamReader(gnatls.getInputStream())); final Optional adaIncludePath = diff --git a/testsuite/drivers/base_driver.py b/testsuite/drivers/base_driver.py index ebb2375c7..b23fce81e 100644 --- a/testsuite/drivers/base_driver.py +++ b/testsuite/drivers/base_driver.py @@ -121,6 +121,10 @@ class BaseDriver(DiffTestDriver): perf_supported = False flag_checking_supported = False + @property + def is_codepeer(self) -> bool: + return self.testsuite_options.codepeer + @property def perf_mode(self) -> bool: return hasattr(self.env, 'perf_mode') and self.env.perf_mode @@ -145,6 +149,7 @@ def test_control_creator(self): return YAMLTestControlCreator({ 'mode': self.env.options.mode, 'os': self.env.build.os.name, + 'is_codepeer': self.is_codepeer, }) def set_up(self) -> None: diff --git a/testsuite/drivers/gnatcheck_driver.py b/testsuite/drivers/gnatcheck_driver.py index 44296b0f4..6d3493b7a 100644 --- a/testsuite/drivers/gnatcheck_driver.py +++ b/testsuite/drivers/gnatcheck_driver.py @@ -152,6 +152,8 @@ class GnatcheckDriver(BaseDriver): to gnatcheck. - ``rules_dirs`` (list[str]): A list of directories to pass to gnatcheck as rule containing directories. + - ``rule_list_file``: If provided, read the given rule file and add its + sorted content to the test output. - ``pre_python``/``post_python`` (str): Python code to be executed before/after the test. @@ -247,7 +249,7 @@ def run_one_test(test_data: dict[str, any]) -> None: output_file_name = self.working_dir( test_data.get( "output_file", - f"gnatcheck.{'xml' if output_format == 'xml' else 'out'}" + f"{exe}.{'xml' if output_format == 'xml' else 'out'}" ) ) @@ -259,6 +261,10 @@ def run_one_test(test_data: dict[str, any]) -> None: if pre_python: capture_exec_python(pre_python) + # Set the codepeer target if needed + if self.is_codepeer: + args.append("--target=codepeer") + # Set the "--show-rule" flag according to the test if test_data.get('show_rule', False): args.append('--show-rule') @@ -372,6 +378,17 @@ def run_one_test(test_data: dict[str, any]) -> None: # Add the execution output to the test output self.output += exec_output + # If requested, add a list of enabled rules to the test output + if test_data.get("rule_list_file", None): + try: + with open( + self.working_dir(test_data["rule_list_file"]), 'r' + ) as f: + self.output += "".join(sorted(f.readlines())) + except FileNotFoundError as _: + self.output += ("testsuite_driver: Cannot found the rule " + f"list file '{test_data['rule_list_file']}'") + if (not brief and p.status not in [0, 1]) or (brief and p.status != 0): self.output += ">>>program returned status code {}\n".format(p.status) diff --git a/testsuite/testsuite.py b/testsuite/testsuite.py index e8becfa66..c1680c0b3 100755 --- a/testsuite/testsuite.py +++ b/testsuite/testsuite.py @@ -92,6 +92,10 @@ def add_options(self, parser: ArgumentParser) -> None: help='The LKQL implementations to test.' ' Possible values are "jit" and "native_jit".' ) + parser.add_argument( + "--codepeer", action="store_true", + help="Run the testsuite with the codepeer target and runtime" + ) parser.add_argument( '--no-auto-path', action='store_true', help='Do not add test programs to the PATH. Useful to test'