// Copyright (c) 2011, David J. Pearce (djp@ecs.vuw.ac.nz) // All rights reserved. // // This software may be modified and distributed under the terms // of the BSD license. See the LICENSE file for details. package wyc.commands; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; import wybs.lang.SyntaxError; import wybs.lang.SyntaxError.InternalFailure; import wybs.util.StdBuildRule; import wybs.util.StdProject; import wyc.builder.CompileTask; import wyc.lang.WhileyFile; import wyc.util.AbstractProjectCommand; import wycc.lang.Feature.ConfigurationError; import wycc.util.ArrayUtils; import wycc.util.Logger; import wyal.lang.WyalFile; import wyfs.lang.Content; import wyfs.lang.Path; import wyfs.util.DirectoryRoot; import wyfs.util.VirtualRoot; import wyil.builders.Wyil2WyalBuilder; import wyil.lang.WyilFile; import wytp.provers.AutomatedTheoremProver; public class Compile extends AbstractProjectCommand<Compile.Result> { /** * Result kind for this command * */ public enum Result { SUCCESS, ERRORS, INTERNAL_FAILURE } /** * Provides a generic place to which normal output should be directed. This * should eventually be replaced. */ private final PrintStream sysout; /** * Provides a generic place to which error output should be directed. This * should eventually be replaced. */ private final PrintStream syserr; /** * Signals that verbose output should be produced. */ protected boolean verbose = false; /** * Signals that brief error reporting should be used. This is primarily used * to help integration with external tools. More specifically, brief output * is structured so as to be machine readable. */ protected boolean brief = false; /** * Signals that compile-time verification of source files should be * performed. */ protected boolean verify = false; /** * Identifies which whiley source files should be considered for * compilation. By default, all files reachable from srcdir are considered. */ protected Content.Filter<WhileyFile> whileyIncludes = Content.filter("**", WhileyFile.ContentType); /** * Identifies which whiley sources files should not be considered for * compilation. This overrides any identified by <code>whileyIncludes</code> * . By default, no files files reachable from srcdir are excluded. */ protected Content.Filter<WhileyFile> whileyExcludes = null; /** * Construct a new instance of this command. * * @param registry * The content registry being used to match files to content * types. * @throws IOException */ public Compile(Content.Registry registry, Logger logger) { super(registry, logger); this.sysout = System.out; this.syserr = System.err; } /** * Construct a new instance of this command. * * @param registry * The content registry being used to match files to content * types. * @throws IOException */ public Compile(Content.Registry registry, Logger logger, OutputStream sysout, OutputStream syserr) { super(registry, logger); this.sysout = new PrintStream(sysout); this.syserr = new PrintStream(syserr); } @Override public String getName() { return "compile"; } // ======================================================================= // Configuration // ======================================================================= private static final String[] SCHEMA = { "verbose", "verify", "brief" }; @Override public String[] getOptions() { return ArrayUtils.append(super.getOptions(),SCHEMA); } @Override public String describe(String option) { switch(option) { case "verbose": return "Enable verbose output from Whiley compiler"; case "brief": return "Enable brief reporting of error messages"; case "verify": return "Enable verification of Whiley source files"; default: return super.describe(option); } } @Override public void set(String option, Object value) throws ConfigurationError { switch(option) { case "verbose": this.verbose = true; break; case "brief": this.brief = true; break; case "verify": this.verify = true; break; default: super.set(option, value); } } @Override public String getDescription() { return "Compile one or more Whiley source files"; } public void setVerify() { verify = true; } public boolean getVerify() { return verify; } public void setVerbose() { setVerbose(true); } public void setVerbose(boolean b) { verbose = b; } public void setBrief() { brief = true; } public String describeIncludes() { return "Specify where find Whiley source files"; } public void setIncludes(Content.Filter<WhileyFile> includes) { this.whileyIncludes = includes; } public String describeExcludes() { return "Specify Whiley source files to be excluded from consideration"; } public void setExcludes(Content.Filter<WhileyFile> excludes) { this.whileyExcludes = excludes; } // ======================================================================= // Execute // ======================================================================= @Override public Result execute(String... args) { try { ArrayList<File> delta = new ArrayList<>(); for (String arg : args) { delta.add(new File(arg)); } // FIXME: somehow, needing to use physical files at this point is // rather cumbersome. It would be much better if the enclosing // framework could handle this aspect for us. for (File f : delta) { if (!f.exists()) { // FIXME: sort this out! sysout.println("compile: file not found: " + f.getName()); return Result.ERRORS; } } // Finalise the configuration before continuing. StdProject project = initialiseProject(); // Determine source files to build List<Path.Entry<WhileyFile>> entries = whileydir.find(delta, WhileyFile.ContentType); // Execute the build over the set of files requested return compile(project,entries); } catch(RuntimeException e) { throw e; } catch (Exception e) { // FIXME: this is a problem because it is swallowing exceptions!! return Result.INTERNAL_FAILURE; } } public Result execute(List<Path.Entry<WhileyFile>> entries) { try { StdProject project = initialiseProject(); return compile(project,entries); } catch (RuntimeException e) { throw e; } catch (Exception e) { // FIXME: this is a problem because it is swallowing exceptions!! return Result.INTERNAL_FAILURE; } } // ======================================================================= // Helpers // ======================================================================= private Result compile(StdProject project, List<Path.Entry<WhileyFile>> entries) { // Initialise Project try { addCompilationBuildRules(project); if (verify) { addVerificationBuildRules(project); } // ===================================================================== // Build Delta + Santity Check // ===================================================================== // Build the source files project.build(entries); // Force all binary files to be written to disk (if appropriate) wyildir.flush(); wyaldir.flush(); // return Result.SUCCESS; } catch(InternalFailure e) { throw e; } catch (SyntaxError e) { e.outputSourceError(syserr, brief); if (verbose) { printStackTrace(syserr, e); } return Result.ERRORS; } catch (Exception e) { // now what? throw new RuntimeException(e); } } /** * Add build rules necessary for compiling whiley source files into binary * wyil files. * * @param project */ protected void addCompilationBuildRules(StdProject project) { addWhiley2WyilBuildRule(project); } /** * Add the rule for compiling Whiley source files into WyIL files. * * @param project */ protected void addWhiley2WyilBuildRule(StdProject project) { // Rule for compiling Whiley to WyIL CompileTask wyilBuilder = new CompileTask(project); if(verbose) { wyilBuilder.setLogger(logger); } project.add(new StdBuildRule(wyilBuilder, whileydir, whileyIncludes, whileyExcludes, wyildir)); } /** * Add build rules necessary for compiling wyil binary files into wyal files * for verification. * * @param project */ protected void addVerificationBuildRules(StdProject project) { // Configure build rules for verification (if applicable) Content.Filter<WyilFile> wyilIncludes = Content.filter("**", WyilFile.ContentType); Content.Filter<WyilFile> wyilExcludes = null; Content.Filter<WyalFile> wyalIncludes = Content.filter("**", WyalFile.ContentType); Content.Filter<WyalFile> wyalExcludes = null; // Rule for compiling WyIL to WyAL Wyil2WyalBuilder wyalBuilder = new Wyil2WyalBuilder(project); if(verbose) { wyalBuilder.setLogger(logger); } project.add(new StdBuildRule(wyalBuilder, wyildir, wyilIncludes, wyilExcludes, wyaldir)); // wytp.types.TypeSystem typeSystem = new wytp.types.TypeSystem(project); AutomatedTheoremProver prover = new AutomatedTheoremProver(typeSystem); wyal.tasks.CompileTask wyalBuildTask = new wyal.tasks.CompileTask(project,typeSystem,prover); if(verbose) { wyalBuildTask.setLogger(logger); } if(verify) { wyalBuildTask.setVerify(true); } project.add(new StdBuildRule(wyalBuildTask, wyaldir, wyalIncludes, wyalExcludes, wycsdir)); } public List getModifiedSourceFiles() throws IOException { if (whileydir == null) { // Note, whileyDir can be null if e.g. compiling wyil -> wyjc return new ArrayList(); } else { return getModifiedSourceFiles(whileydir, whileyIncludes, wyildir, WyilFile.ContentType); } } /** * Generate the list of source files which need to be recompiled. By * default, this is done by comparing modification times of each whiley file * against its corresponding wyil file. Wyil files which are out-of-date are * scheduled to be recompiled. * * @return * @throws IOException */ public static <T, S> List<Path.Entry<T>> getModifiedSourceFiles(Path.Root sourceDir, Content.Filter<T> sourceIncludes, Path.Root binaryDir, Content.Type<S> binaryContentType) throws IOException { // Now, touch all source files which have modification date after // their corresponding binary. ArrayList<Path.Entry<T>> sources = new ArrayList<>(); for (Path.Entry<T> source : sourceDir.get(sourceIncludes)) { // currently, I'm assuming everything is modified! Path.Entry<S> binary = binaryDir.get(source.id(), binaryContentType); // first, check whether wycs file out-of-date with source file if (binary == null || binary.lastModified() < source.lastModified()) { sources.add(source); } } return sources; } /** * Print a complete stack trace. This differs from * Throwable.printStackTrace() in that it always prints all of the trace. * * @param out * @param err */ private static void printStackTrace(PrintStream out, Throwable err) { out.println(err.getClass().getName() + ": " + err.getMessage()); for(StackTraceElement ste : err.getStackTrace()) { out.println("\tat " + ste.toString()); } if(err.getCause() != null) { out.print("Caused by: "); printStackTrace(out,err.getCause()); } } }