33 * SPDX-License-Identifier: GPL-2.0-only */
44package de .uka .ilkd .key .gui .plugins .javac ;
55
6- import java .io .File ;
7- import java .io .IOException ;
8- import java .io .OutputStream ;
9- import java .io .StringWriter ;
6+ import java .io .*;
107import java .net .URI ;
118import java .net .URISyntaxException ;
129import java .nio .charset .Charset ;
1310import java .nio .file .Files ;
1411import java .nio .file .Path ;
12+ import java .nio .file .Paths ;
1513import java .util .*;
1614import java .util .concurrent .CompletableFuture ;
1715import java .util .stream .Collectors ;
2018import de .uka .ilkd .key .gui .PositionedIssueString ;
2119import de .uka .ilkd .key .java .Position ;
2220import de .uka .ilkd .key .parser .Location ;
23- import de .uka .ilkd .key .proof . init . ProblemInitializer ;
21+ import de .uka .ilkd .key .settings . Configuration ;
2422
23+ import org .key_project .util .Streams ;
24+
25+ import org .antlr .v4 .runtime .CharStreams ;
2526import org .jspecify .annotations .NonNull ;
2627import org .slf4j .Logger ;
2728import org .slf4j .LoggerFactory ;
3334 * <p>
3435 * For setting up <code>javac</code> it uses the KeY project information about the bootpath and
3536 * classpath.
36- * Any issues found in the compilation are reported to a provided listener of type
37- * {@link ProblemInitializer.ProblemInitializerListener}.
3837 * <p>
3938 * Checking the target Java code can be enabled / disabled by providing the property
4039 * <code>-PKEY_JAVAC_DISABLE=true</code> / <code>-PKEY_JAVAC_DISABLE=false</code> on startup of KeY.
4342 * @version 1 (14.10.22)
4443 */
4544public class JavaCompilerCheckFacade {
45+ private JavaCompilerCheckFacade () {
46+ /* This utility class should not be instantiated */
47+ }
48+
4649 private static final Logger LOGGER = LoggerFactory .getLogger (JavaCompilerCheckFacade .class );
4750
51+ /**
52+ * This main method be exclusively used by `checkExternally` to perform javac checks
53+ */
54+ public static void main (String [] args ) {
55+ try (var input = new InputStreamReader (System .in )) {
56+ Configuration params = Configuration .load (CharStreams .fromReader (input ));
57+
58+ var settings = new JavacSettings ();
59+ settings .readSettings (params );
60+
61+ List <PositionedIssueString > result = check (
62+ Paths .get (params .getString ("bootClassPath" )),
63+ params .getStringList ("classPath" ).stream ().map (Paths ::get ).toList (),
64+ Paths .get (params .getString ("javaPath" )), settings ).get ();
65+
66+ var out = new Configuration ();
67+ out .set ("messages" ,
68+ result .stream ().map (it -> {
69+ var map = Map .of (
70+ "message" , it .text ,
71+ "kind" , it .getKind ().toString (),
72+ "line" , it .location .getPosition ().line (),
73+ "column" , it .location .getPosition ().column (),
74+ "additionalInfo" , it .getAdditionalInfo ());
75+ if (it .location .fileUri () != null ) {
76+ map = new TreeMap <>(map );
77+ map .put ("fileUri" , it .location .fileUri ().toString ());
78+ }
79+ return map ;
80+ }).toList ());
81+
82+ // send the data over stderr to not interfere with the log messages
83+ Writer writer = new OutputStreamWriter (System .err );
84+ out .save (writer , null );
85+ writer .close ();
86+ } catch (Exception e ) {
87+ LOGGER .error ("Error during execution." , e );
88+ }
89+ }
90+
91+ /**
92+ * initiates the compilation check on the target Java source (the Java program to be verified)
93+ * in a separate process (with another java runtime)
94+ *
95+ * @param bootClassPath the {@link Path} referring to the path containing the core Java classes
96+ * @param classPath the {@link List} of {@link Path}s referring to the directory that make up
97+ * the target Java programs classpath
98+ * @param javaPath the {@link Path} to the source of the target Java program
99+ * @param settings the {@link JavacSettings} that describe what other options the compiler
100+ * should be called with
101+ * @return future providing the list of diagnostics
102+ */
103+ public static @ NonNull CompletableFuture <List <PositionedIssueString >> checkExternally (
104+ Path bootClassPath , List <Path > classPath , Path javaPath ,
105+ JavacSettings settings ) {
106+ if (Boolean .getBoolean ("KEY_JAVAC_DISABLE" )) {
107+ LOGGER .info ("Javac check is disabled by system property -PKEY_JAVAC_DISABLE" );
108+ return CompletableFuture .completedFuture (Collections .emptyList ());
109+ }
110+ LOGGER .info (
111+ "External Javac check is triggered. To disable use property -PKEY_JAVAC_DISABLE=true" );
112+ var params = new Configuration ();
113+ params .set ("bootClassPath" , Objects .toString (bootClassPath ));
114+ params .set ("classPath" ,
115+ classPath .stream ().map (Path ::toAbsolutePath ).map (Path ::toString ).toList ());
116+ params .set ("javaPath" , Objects .toString (javaPath ));
117+ settings .writeSettings (params );
118+
119+ String classpath = System .getProperty ("java.class.path" );
120+ String path =
121+ Paths .get (System .getProperty ("java.home" ), "bin" , "java" ).toAbsolutePath ().toString ();
122+ ProcessBuilder processBuilder =
123+ new ProcessBuilder (path ,
124+ "--add-exports" , "jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED" ,
125+ "--add-exports" , "jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED" ,
126+ "--add-exports" , "jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED" ,
127+ "--add-exports" , "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED" ,
128+ "--add-exports" , "jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED" ,
129+ "--add-exports" , "jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED" ,
130+ "--add-exports" , "jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED" ,
131+ "--add-exports" , "jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED" ,
132+ "--add-opens" , "jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED" ,
133+ "-cp" ,
134+ classpath ,
135+ JavaCompilerCheckFacade .class .getCanonicalName ());
136+ return CompletableFuture .supplyAsync (() -> {
137+ try {
138+ var process = processBuilder .start ();
139+ params .save (process .outputWriter (), null );
140+ process .outputWriter ().close ();
141+
142+ // the KeY logging messages from `process` (currently not used)
143+ String logs = Streams .toString (process .getInputStream ());
144+ Configuration messages =
145+ Configuration .load (CharStreams .fromStream (process .getErrorStream ()));
146+ process .waitFor ();
147+
148+ return messages .getList ("messages" ).stream ()
149+ .map (msgObj -> {
150+ Configuration msg = (Configuration ) msgObj ;
151+ return new PositionedIssueString (
152+ msg .getString ("message" ),
153+ new Location (
154+ msg .exists ("fileUri" )
155+ ? URI .create (msg .getString ("fileUri" ))
156+ : null ,
157+ msg .getInt ("line" ) == -1 || msg .getInt ("column" ) == -1
158+ ? Position .UNDEFINED
159+ : Position .newOneBased (
160+ msg .getInt ("line" ),
161+ msg .getInt ("column" ))),
162+ msg .getString ("additionalInfo" ),
163+ PositionedIssueString .Kind .valueOf (msg .getString ("kind" )));
164+ }).toList ();
165+ } catch (IOException | InterruptedException e ) {
166+ throw new RuntimeException (e );
167+ }
168+ });
169+ }
170+
48171 /**
49172 * initiates the compilation check on the target Java source (the Java program to be verified)
50- * and
51- * reports any issues to the provided <code>listener</code>
52173 *
53- * @param listener the {@link ProblemInitializer.ProblemInitializerListener} to be informed
54- * about any issues found in the target Java program
55- * @param bootClassPath the {@link File} referring to the path containing the core Java classes
56- * @param classPath the {@link List} of {@link File}s referring to the directory that make up
174+ * @param bootClassPath the {@link Path} referring to the path containing the core Java classes
175+ * @param classPath the {@link List} of {@link Path}s referring to the directory that make up
57176 * the target Java programs classpath
58- * @param javaPath the {@link String} with the path to the source of the target Java program
177+ * @param javaPath the {@link Path} to the source of the target Java program
178+ * @param settings the {@link JavacSettings} that describe what other options the compiler
179+ * should be called with
59180 * @return future providing the list of diagnostics
60181 */
61182 public static @ NonNull CompletableFuture <List <PositionedIssueString >> check (
62- ProblemInitializer . ProblemInitializerListener listener ,
63- Path bootClassPath , List < Path > classPath , Path javaPath ) {
183+ Path bootClassPath , List < Path > classPath , Path javaPath ,
184+ JavacSettings settings ) {
64185 if (Boolean .getBoolean ("KEY_JAVAC_DISABLE" )) {
65186 LOGGER .info ("Javac check is disabled by system property -PKEY_JAVAC_DISABLE" );
66187 return CompletableFuture .completedFuture (Collections .emptyList ());
@@ -72,7 +193,6 @@ public class JavaCompilerCheckFacade {
72193
73194 if (compiler == null ) {
74195 LOGGER .info ("Javac is not available in current java runtime. Javac check skipped" );
75- listener .reportStatus (null , "No javac compiler found. Java check disabled." );
76196 return CompletableFuture .completedFuture (Collections .emptyList ());
77197 }
78198
@@ -86,17 +206,47 @@ public class JavaCompilerCheckFacade {
86206
87207 // gather configured bootstrap classpath and regular classpath
88208 List <String > options = new ArrayList <>();
209+
210+ if (settings .getUseProcessors ()) {
211+ String newlineClassPath = settings .getClassPaths ();
212+ List <Path > processorClassPath =
213+ Arrays .asList (newlineClassPath .split (System .lineSeparator ()))
214+ .stream ()
215+ .filter (s -> !s .isBlank ())
216+ .map (Paths ::get )
217+ .toList ();
218+
219+ if (!processorClassPath .isEmpty ()) {
220+ classPath = new ArrayList <>(classPath );
221+ classPath .addAll (processorClassPath );
222+ }
223+
224+ List <String > processors =
225+ Arrays .asList (settings .getProcessors ().split (System .lineSeparator ()))
226+ .stream ()
227+ .filter (s -> !s .isBlank ())
228+ .toList ();
229+
230+ if (!processors .isEmpty ()) {
231+ options .add ("-processor" );
232+ options .add (processors .stream ().collect (Collectors .joining ("," )));
233+ }
234+ }
235+
89236 if (bootClassPath != null ) {
90- options .add ("-bootclasspath" );
91- options .add (bootClassPath .toAbsolutePath ().toString ());
237+ // options.add("-bootclasspath");
238+ // options.add(bootClassPath.toAbsolutePath().toString());
239+ LOGGER .info ("The \" bootclasspath\" Option is set but not supported." );
92240 }
241+
93242 if (classPath != null && !classPath .isEmpty ()) {
94243 options .add ("-classpath" );
95244 options .add (
96245 classPath .stream ().map (Path ::toAbsolutePath )
97246 .map (Objects ::toString )
98247 .collect (Collectors .joining (":" )));
99248 }
249+
100250 ArrayList <Path > files = new ArrayList <>();
101251 if (Files .isDirectory (javaPath )) {
102252 try (var s = Files .walk (javaPath )) {
@@ -113,6 +263,10 @@ public class JavaCompilerCheckFacade {
113263 Iterable <? extends JavaFileObject > compilationUnits =
114264 fileManager .getJavaFileObjects (files .toArray (new Path [0 ]));
115265
266+ LOGGER .info (
267+ "running Javac check with following\n \t options: {},\n \t classes: {},\n \t compilation units: {}," ,
268+ options , classes , compilationUnits );
269+
116270 JavaCompiler .CompilationTask task = compiler .getTask (output , fileManager , diagnostics ,
117271 options , classes , compilationUnits );
118272
@@ -127,7 +281,9 @@ public class JavaCompilerCheckFacade {
127281 it -> new PositionedIssueString (
128282 it .getMessage (Locale .ENGLISH ),
129283 new Location (
130- fileManager .asPath (it .getSource ()).toFile ().toPath ().toUri (),
284+ it .getSource () == null
285+ ? null
286+ : fileManager .asPath (it .getSource ()).toFile ().toPath ().toUri (),
131287 it .getPosition () != Diagnostic .NOPOS
132288 ? Position .newOneBased ((int ) it .getLineNumber (),
133289 (int ) it .getColumnNumber ())
0 commit comments