From 602054888d037552d88186b530fa4ad624fc3e6c Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 10:40:20 +0100 Subject: [PATCH 01/30] avoid the copy constructor in FileConfig --- org.lflang/src/org/lflang/FileConfig.java | 42 +------------------ .../org/lflang/federated/FedFileConfig.java | 21 +++------- .../lflang/generator/rust/RustFileConfig.kt | 5 +-- 3 files changed, 9 insertions(+), 59 deletions(-) diff --git a/org.lflang/src/org/lflang/FileConfig.java b/org.lflang/src/org/lflang/FileConfig.java index 2ced9e3ed5..2562f8d54c 100644 --- a/org.lflang/src/org/lflang/FileConfig.java +++ b/org.lflang/src/org/lflang/FileConfig.java @@ -146,7 +146,7 @@ public class FileConfig { * to the package root, then the generated sources will be put in x/y/Z * relative to srcGenBasePath. */ - private Path srcGenPath; + protected Path srcGenPath; // private fields @@ -181,45 +181,12 @@ public FileConfig(Resource resource, Path srcGenBasePath, LFGeneratorContext con this.srcGenBasePath = srcGenBasePath; this.name = nameWithoutExtension(this.srcFile); - this.srcGenPath = getSrcGenPath(this.srcGenBasePath, this.srcPkgPath, - this.srcPath, name); + this.srcGenPath = srcGenBasePath.resolve(getSubPkgPath(srcPkgPath, srcPath)).resolve(name); this.srcGenPkgPath = this.srcGenPath; this.outPath = srcGenBasePath.getParent(); this.binPath = getBinPath(this.srcPkgPath, this.srcPath, this.outPath, context); this.iResource = getIResource(resource); } -/** - * A copy constructor for FileConfig objects. Children of this class can - * use this constructor to obtain a copy of a parent object. - * - * @param fileConfig An object of FileConfig - * @throws IOException If the resource tracked by {@code fileConfig} has an invalid URI - */ - protected FileConfig(FileConfig fileConfig) throws IOException { - this.resource = fileConfig.resource; - this.context = fileConfig.context; - - this.srcFile = fileConfig.srcFile; - - this.srcPath = srcFile.getParent(); - this.srcPkgPath = fileConfig.srcPkgPath; - - this.srcGenBasePath = fileConfig.srcGenBasePath; - this.name = nameWithoutExtension(this.srcFile); - this.srcGenPath = getSrcGenPath(this.srcGenBasePath, this.srcPkgPath, - this.srcPath, name); - this.srcGenPkgPath = this.srcGenPath; - this.outPath = srcGenBasePath.getParent(); - this.binPath = getBinPath(this.srcPkgPath, this.srcPath, this.outPath, context); - this.iResource = getIResource(resource); - } - - // Getters to be overridden in derived classes. - - protected void setSrcGenPath(Path srcGenPath) { - this.srcGenPath = srcGenPath; - } - /** * Get the iResource corresponding to the provided resource if it can be @@ -368,11 +335,6 @@ public static Path getSrcGenRoot(IFileSystemAccess2 fsa) throws IOException { return FileConfig.toPath(srcGenURI); } - protected static Path getSrcGenPath(Path srcGenRootPath, Path pkgPath, - Path srcPath, String name) { - return srcGenRootPath.resolve(getSubPkgPath(pkgPath, srcPath)).resolve(name); - } - /** * Given a path that denotes the root of the package and a path * that denotes the full path to a source file (not including the diff --git a/org.lflang/src/org/lflang/federated/FedFileConfig.java b/org.lflang/src/org/lflang/federated/FedFileConfig.java index 130f2eebcd..71d2ebc54b 100644 --- a/org.lflang/src/org/lflang/federated/FedFileConfig.java +++ b/org.lflang/src/org/lflang/federated/FedFileConfig.java @@ -41,17 +41,7 @@ public class FedFileConfig extends FileConfig { /** Name of the federate for this FedFileConfig */ - protected String federateName; - - /** - * Copy constructor for FedFileConfig. - * - * @param fedFileConfig The existing instance of 'FedFileConfig'. - * @throws IOException - */ - public FedFileConfig(FedFileConfig fedFileConfig) throws IOException { - this(fedFileConfig, fedFileConfig.federateName); - } + protected final String federateName; /** * Create an instance of FedFileConfig for federate 'federateName' from an existing @@ -61,13 +51,12 @@ public FedFileConfig(FedFileConfig fedFileConfig) throws IOException { * @param federateName The name of the federate. * @throws IOException */ - public FedFileConfig(FileConfig fileConfig, String federateName) throws IOException { - super(fileConfig); + public FedFileConfig(final FileConfig fileConfig, final String federateName) throws IOException { + super(fileConfig.resource, fileConfig.getSrcGenBasePath(), fileConfig.context); this.federateName = federateName; - // The generated code for each federate should be located at super.getSrcGenPath() + "/federateName/" - this.setSrcGenPath(getSrcGenPath(this.srcGenBasePath, this.srcPkgPath, - this.srcPath, name + File.separator + federateName)); + // The generated code for each federate should be located at fileConfig.srcGenPath + "/federateName/" + this.srcGenPath = fileConfig.getSrcGenPath().resolve(federateName); } } diff --git a/org.lflang/src/org/lflang/generator/rust/RustFileConfig.kt b/org.lflang/src/org/lflang/generator/rust/RustFileConfig.kt index afe96b3667..c44766f8f2 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustFileConfig.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustFileConfig.kt @@ -52,9 +52,8 @@ class RustFileConfig(resource: Resource, srcGenBasePath: Path, context: LFGenera } } - inline fun emit(codeMaps: MutableMap, pathRelativeToOutDir: String, f: Emitter.() -> Unit): Unit - = emit(codeMaps, srcGenPath.resolve(pathRelativeToOutDir), f) - + inline fun emit(codeMaps: MutableMap, pathRelativeToOutDir: String, f: Emitter.() -> Unit): Unit = + emit(codeMaps, getSrcGenPath().resolve(pathRelativeToOutDir), f) } /** From 1e164d7e0bc9b8a27e6430d34c3071fc33bbce09 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 12:40:54 +0100 Subject: [PATCH 02/30] move cleanIfNeeded to generator base This enables removing the dependency on context from FileConfig --- org.lflang/src/org/lflang/FileConfig.java | 14 -------------- .../src/org/lflang/federated/FedFileConfig.java | 1 - .../org/lflang/generator/GeneratorBase.xtend | 17 ++++++++++++++++- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/org.lflang/src/org/lflang/FileConfig.java b/org.lflang/src/org/lflang/FileConfig.java index 2562f8d54c..14e645526a 100644 --- a/org.lflang/src/org/lflang/FileConfig.java +++ b/org.lflang/src/org/lflang/FileConfig.java @@ -641,20 +641,6 @@ public String copyFileOrResource(String fileName, Path srcDir, Path dstDir) { return ""; } - /** - * Check if a clean was requested from the standalone compiler and perform - * the clean step. - */ - public void cleanIfNeeded() { - if (context.getArgs().containsKey("clean")) { - try { - doClean(); - } catch (IOException e) { - System.err.println("WARNING: IO Error during clean"); - } - } - } - /** * Recursively delete a directory if it exists. * diff --git a/org.lflang/src/org/lflang/federated/FedFileConfig.java b/org.lflang/src/org/lflang/federated/FedFileConfig.java index 71d2ebc54b..14baea8e66 100644 --- a/org.lflang/src/org/lflang/federated/FedFileConfig.java +++ b/org.lflang/src/org/lflang/federated/FedFileConfig.java @@ -25,7 +25,6 @@ package org.lflang.federated; -import java.io.File; import java.io.IOException; import org.lflang.FileConfig; diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 849e11169b..2712e61cad 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -25,6 +25,7 @@ package org.lflang.generator import java.io.File +import java.io.IOException; import java.nio.file.Files import java.nio.file.Paths import java.util.ArrayList @@ -288,7 +289,7 @@ abstract class GeneratorBase extends AbstractLFValidator { context, JavaGeneratorUtils.findTarget(fileConfig.resource), targetConfig, errorReporter ) - fileConfig.cleanIfNeeded() + cleanIfNeeded(context) printInfo() @@ -351,6 +352,20 @@ abstract class GeneratorBase extends AbstractLFValidator { enableSupportForSerializationIfApplicable(context.cancelIndicator); } + /** + * Check if a clean was requested from the standalone compiler and perform + * the clean step. + */ + protected def cleanIfNeeded(LFGeneratorContext context) { + if (context.getArgs().containsKey("clean")) { + try { + fileConfig.doClean(); + } catch (IOException e) { + System.err.println("WARNING: IO Error during clean"); + } + } + } + /** * Create a new instantiation graph. This is a graph where each node is a Reactor (not a ReactorInstance) * and an arc from Reactor A to Reactor B means that B contains an instance of A, constructed with a statement From 574dadce2fcf75315b40672e84f7dd3994cff787 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 13:36:37 +0100 Subject: [PATCH 03/30] remove unused function from GeneratorBase --- .../org/lflang/generator/GeneratorBase.xtend | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 2712e61cad..3bed747eeb 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -566,54 +566,6 @@ abstract class GeneratorBase extends AbstractLFValidator { return unit.canonicalName.toUpperCase } - /** - * Run the custom build command specified with the "build" parameter. - * This command is executed in the same directory as the source file. - * - * The following environment variables will be available to the command: - * - * * LF_CURRENT_WORKING_DIRECTORY: The directory in which the command is invoked. - * * LF_SOURCE_DIRECTORY: The directory containing the .lf file being compiled. - * * LF_SOURCE_GEN_DIRECTORY: The directory in which generated files are placed. - * * LF_BIN_DIRECTORY: The directory into which to put binaries. - * - */ - protected def runBuildCommand() { - var commands = new ArrayList - for (cmd : targetConfig.buildCommands) { - val tokens = newArrayList(cmd.split("\\s+")) - if (tokens.size > 0) { - val buildCommand = commandFactory.createCommand( - tokens.head, - tokens.tail.toList, - this.fileConfig.srcPath - ) - // If the build command could not be found, abort. - // An error has already been reported in createCommand. - if (buildCommand === null) { - return - } - commands.add(buildCommand) - } - } - - for (cmd : commands) { - // execute the command - val returnCode = cmd.run() - - if (returnCode != 0 && fileConfig.context.mode === Mode.STANDALONE) { - errorReporter.reportError('''Build command "«targetConfig.buildCommands»" returns error code «returnCode»''') - return - } - // For warnings (vs. errors), the return code is 0. - // But we still want to mark the IDE. - if (cmd.errors.toString.length > 0 && fileConfig.context.mode !== Mode.STANDALONE) { - reportCommandErrors(cmd.errors.toString()) - return - } - } - } - // ////////////////////////////////////////// // // Protected methods. From 8f87e154896ece32e2a6acc9a407a67be1acdfb6 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 14:10:56 +0100 Subject: [PATCH 04/30] move Mode enum to LFGeneratorContext (where it is used) --- org.lflang.lfc/src/org/lflang/lfc/Main.java | 3 +- org.lflang/src/org/lflang/TargetConfig.java | 9 ---- .../generator/EclipseErrorReporter.java | 3 +- .../org/lflang/generator/GeneratorBase.xtend | 7 ++-- .../lflang/generator/IntegratedBuilder.java | 4 +- .../lflang/generator/JavaGeneratorUtils.java | 9 +--- .../src/org/lflang/generator/LFGenerator.java | 4 +- .../lflang/generator/LFGeneratorContext.java | 10 ++++- .../src/org/lflang/generator/MainContext.java | 1 - .../src/org/lflang/generator/SubContext.java | 1 - .../src/org/lflang/generator/Validator.java | 3 +- .../lflang/generator/c/CCmakeCompiler.java | 9 ++-- .../src/org/lflang/generator/c/CCompiler.java | 7 ++-- .../org/lflang/generator/c/CGenerator.xtend | 3 +- .../src/org/lflang/generator/c/CUtil.java | 8 ++-- .../org/lflang/generator/cpp/CppGenerator.kt | 2 +- .../generator/python/PythonGenerator.xtend | 3 +- .../lflang/generator/rust/RustGenerator.kt | 4 +- .../org/lflang/generator/ts/TSGenerator.kt | 42 +++++++++---------- 19 files changed, 55 insertions(+), 77 deletions(-) diff --git a/org.lflang.lfc/src/org/lflang/lfc/Main.java b/org.lflang.lfc/src/org/lflang/lfc/Main.java index b30ae12e2f..cce1c0d6b2 100644 --- a/org.lflang.lfc/src/org/lflang/lfc/Main.java +++ b/org.lflang.lfc/src/org/lflang/lfc/Main.java @@ -35,7 +35,6 @@ import org.lflang.FileConfig; import org.lflang.LFRuntimeModule; import org.lflang.LFStandaloneSetup; -import org.lflang.TargetConfig.Mode; import org.lflang.generator.LFGeneratorContext; import org.lflang.generator.MainContext; @@ -273,7 +272,7 @@ private void runGenerator(List files, Injector injector) { exitIfCollectedErrors(); LFGeneratorContext context = new MainContext( - Mode.STANDALONE,CancelIndicator.NullImpl, (m, p) -> {}, properties, false, + LFGeneratorContext.Mode.STANDALONE, CancelIndicator.NullImpl, (m, p) -> {}, properties, false, fileConfig -> injector.getInstance(ErrorReporter.class) ); diff --git a/org.lflang/src/org/lflang/TargetConfig.java b/org.lflang/src/org/lflang/TargetConfig.java index ae0631c0c5..9df154dc9d 100644 --- a/org.lflang/src/org/lflang/TargetConfig.java +++ b/org.lflang/src/org/lflang/TargetConfig.java @@ -303,15 +303,6 @@ public static class DockerOptions { public String from = "alpine:latest"; } - public enum Mode { - STANDALONE, - EPOCH, - LSP_FAST, - LSP_MEDIUM, - LSP_SLOW, - UNDEFINED - } - /** * Settings related to tracing options. */ diff --git a/org.lflang/src/org/lflang/generator/EclipseErrorReporter.java b/org.lflang/src/org/lflang/generator/EclipseErrorReporter.java index a09295a691..b7ad052258 100644 --- a/org.lflang/src/org/lflang/generator/EclipseErrorReporter.java +++ b/org.lflang/src/org/lflang/generator/EclipseErrorReporter.java @@ -38,7 +38,6 @@ import org.eclipse.xtext.validation.EObjectDiagnosticImpl; import org.lflang.ErrorReporter; import org.lflang.FileConfig; -import org.lflang.TargetConfig.Mode; /** * An error reporter that prints messages to the command line output and also @@ -113,7 +112,7 @@ private String report(String message, Severity severity, Integer line, Path file // error. // See: // https://help.eclipse.org/2020-03/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Fguide%2FresAdv_markers.htm - if (fileConfig.context.getMode() == Mode.EPOCH) { + if (fileConfig.context.getMode() == LFGeneratorContext.Mode.EPOCH) { final IResource iResource = file != null ? fileConfig.getIResource(file) : fileConfig.iResource; diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 3bed747eeb..f5eb62079d 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -47,7 +47,6 @@ import org.lflang.InferredType import org.lflang.MainConflictChecker import org.lflang.Target import org.lflang.TargetConfig -import org.lflang.TargetConfig.Mode import org.lflang.TargetProperty.CoordinationType import org.lflang.TimeUnit import org.lflang.TimeValue @@ -304,7 +303,7 @@ abstract class GeneratorBase extends AbstractLFValidator { createMainInstantiation() // Check if there are any conflicting main reactors elsewhere in the package. - if (context.mode == Mode.STANDALONE && mainDef !== null) { + if (context.mode == LFGeneratorContext.Mode.STANDALONE && mainDef !== null) { for (String conflict : new MainConflictChecker(fileConfig).conflicts) { errorReporter.reportError(this.mainDef.reactorClass, "Conflicting main reactor in " + conflict); } @@ -312,7 +311,7 @@ abstract class GeneratorBase extends AbstractLFValidator { // Configure the command factory commandFactory.setVerbose(); - if (context.mode == Mode.STANDALONE && context.getArgs().containsKey("quiet")) { + if (context.mode == LFGeneratorContext.Mode.STANDALONE && context.getArgs().containsKey("quiet")) { commandFactory.setQuiet(); } @@ -386,7 +385,7 @@ abstract class GeneratorBase extends AbstractLFValidator { // If there is no main reactor or if all reactors in the file need to be validated, then make sure the reactors // list includes even reactors that are not instantiated anywhere. - if (mainDef === null || fileConfig.context.mode == Mode.LSP_MEDIUM) { + if (mainDef === null || fileConfig.context.mode == LFGeneratorContext.Mode.LSP_MEDIUM) { for (r : fileConfig.resource.allContents.toIterable.filter(Reactor)) { if (!this.reactors.contains(r)) { this.reactors.add(r); diff --git a/org.lflang/src/org/lflang/generator/IntegratedBuilder.java b/org.lflang/src/org/lflang/generator/IntegratedBuilder.java index 0bab22fff1..f1172cbab9 100644 --- a/org.lflang/src/org/lflang/generator/IntegratedBuilder.java +++ b/org.lflang/src/org/lflang/generator/IntegratedBuilder.java @@ -18,7 +18,7 @@ import org.lflang.ErrorReporter; import org.lflang.FileConfig; -import org.lflang.TargetConfig.Mode; +import org.lflang.generator.LFGeneratorContext.Mode; import com.google.inject.Inject; import com.google.inject.Provider; @@ -125,7 +125,7 @@ private GeneratorResult doGenerate( CancelIndicator cancelIndicator ) { LFGeneratorContext context = new MainContext( - mustComplete ? Mode.LSP_SLOW : Mode.LSP_MEDIUM, cancelIndicator, reportProgress, new Properties(), + mustComplete ? Mode.LSP_SLOW : LFGeneratorContext.Mode.LSP_MEDIUM, cancelIndicator, reportProgress, new Properties(), false, fileConfig -> new LanguageServerErrorReporter(fileConfig.resource.getContents().get(0)) ); generator.generate(getResource(uri), fileAccess, context); diff --git a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java index 607e8547d1..97414cf5e3 100644 --- a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java +++ b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java @@ -1,12 +1,8 @@ package org.lflang.generator; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -19,7 +15,6 @@ import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; -import org.eclipse.xtext.generator.IFileSystemAccess2; import org.eclipse.xtext.generator.IGeneratorContext; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.validation.CheckMode; @@ -29,7 +24,7 @@ import org.lflang.FileConfig; import org.lflang.Target; import org.lflang.TargetConfig; -import org.lflang.TargetConfig.Mode; +import org.lflang.generator.LFGeneratorContext.Mode; import org.lflang.TargetProperty; import org.lflang.graph.InstantiationGraph; import org.lflang.lf.Action; @@ -352,7 +347,7 @@ public static void writeToFile(CharSequence text, Path path) throws IOException * @param compilerMode An indicator of whether Epoch is running. */ public static void refreshProject(Resource resource, Mode compilerMode) { - if (compilerMode == Mode.EPOCH) { + if (compilerMode == LFGeneratorContext.Mode.EPOCH) { URI uri = resource.getURI(); if (uri.isPlatformResource()) { // This condition should normally be met when running Epoch IResource member = ResourcesPlugin.getWorkspace().getRoot().findMember(uri.toPlatformString(true)); diff --git a/org.lflang/src/org/lflang/generator/LFGenerator.java b/org.lflang/src/org/lflang/generator/LFGenerator.java index 688c2814b4..fc7a74c154 100644 --- a/org.lflang/src/org/lflang/generator/LFGenerator.java +++ b/org.lflang/src/org/lflang/generator/LFGenerator.java @@ -6,7 +6,6 @@ import java.nio.file.Path; import java.util.Objects; -import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.AbstractGenerator; import org.eclipse.xtext.generator.IFileSystemAccess2; @@ -17,7 +16,6 @@ import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.Target; -import org.lflang.TargetConfig.Mode; import org.lflang.generator.c.CGenerator; import org.lflang.generator.python.PythonGenerator; import org.lflang.scoping.LFGlobalScopeProvider; @@ -149,7 +147,7 @@ private GeneratorBase createKotlinBaseGenerator(Target target, FileConfig fileCo public void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) { final LFGeneratorContext lfContext = LFGeneratorContext.lfGeneratorContextOf(context, resource); - if (lfContext.getMode() == Mode.LSP_FAST) return; // The fastest way to generate code is to not generate any code. + if (lfContext.getMode() == LFGeneratorContext.Mode.LSP_FAST) return; // The fastest way to generate code is to not generate any code. final Target target = Target.fromDecl(ASTUtils.targetDecl(resource)); assert target != null; diff --git a/org.lflang/src/org/lflang/generator/LFGeneratorContext.java b/org.lflang/src/org/lflang/generator/LFGeneratorContext.java index b4320b3751..fd01380d0d 100644 --- a/org.lflang/src/org/lflang/generator/LFGeneratorContext.java +++ b/org.lflang/src/org/lflang/generator/LFGeneratorContext.java @@ -10,7 +10,6 @@ import org.lflang.ErrorReporter; import org.lflang.FileConfig; -import org.lflang.TargetConfig.Mode; import org.lflang.util.LFCommand; /** @@ -22,6 +21,15 @@ */ public interface LFGeneratorContext extends IGeneratorContext { + enum Mode { + STANDALONE, + EPOCH, + LSP_FAST, + LSP_MEDIUM, + LSP_SLOW, + UNDEFINED + } + /** * Return the mode of operation, which indicates how the compiler has been invoked * (e.g., from within Epoch, from the command line, or via a Language Server). diff --git a/org.lflang/src/org/lflang/generator/MainContext.java b/org.lflang/src/org/lflang/generator/MainContext.java index c786cf2200..ff515f78fd 100644 --- a/org.lflang/src/org/lflang/generator/MainContext.java +++ b/org.lflang/src/org/lflang/generator/MainContext.java @@ -8,7 +8,6 @@ import org.lflang.DefaultErrorReporter; import org.lflang.ErrorReporter; import org.lflang.FileConfig; -import org.lflang.TargetConfig.Mode; import org.lflang.generator.IntegratedBuilder.ReportProgress; /** diff --git a/org.lflang/src/org/lflang/generator/SubContext.java b/org.lflang/src/org/lflang/generator/SubContext.java index 89e0ca7784..fcffaf8806 100644 --- a/org.lflang/src/org/lflang/generator/SubContext.java +++ b/org.lflang/src/org/lflang/generator/SubContext.java @@ -6,7 +6,6 @@ import org.lflang.ErrorReporter; import org.lflang.FileConfig; -import org.lflang.TargetConfig.Mode; /** * A {@code SubContext} is the context of a process within a build process. For example, diff --git a/org.lflang/src/org/lflang/generator/Validator.java b/org.lflang/src/org/lflang/generator/Validator.java index f62f94083c..0ad83f9c35 100644 --- a/org.lflang/src/org/lflang/generator/Validator.java +++ b/org.lflang/src/org/lflang/generator/Validator.java @@ -3,7 +3,6 @@ import org.eclipse.xtext.util.CancelIndicator; import org.lflang.ErrorReporter; -import org.lflang.TargetConfig.Mode; import org.lflang.util.LFCommand; import java.nio.file.Path; import java.util.ArrayList; @@ -88,7 +87,7 @@ private boolean validationEnabled(LFGeneratorContext context) { * @return Whether validation of generated code is enabled by default. */ protected boolean validationEnabledByDefault(LFGeneratorContext context) { - return context.getMode() != Mode.STANDALONE; + return context.getMode() != LFGeneratorContext.Mode.STANDALONE; } /** diff --git a/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java b/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java index 089a967cb7..2c24706abc 100644 --- a/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java +++ b/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java @@ -35,7 +35,6 @@ import org.lflang.ErrorReporter; import org.lflang.FileConfig; -import org.lflang.TargetConfig.Mode; import org.lflang.TargetConfig; import org.lflang.generator.GeneratorBase; import org.lflang.generator.JavaGeneratorUtils; @@ -129,7 +128,7 @@ public boolean runCCompiler( int cMakeReturnCode = compile.run(context.getCancelIndicator()); if (cMakeReturnCode != 0 && - context.getMode() == Mode.STANDALONE && + context.getMode() == LFGeneratorContext.Mode.STANDALONE && !outputContainsKnownCMakeErrors(compile.getErrors().toString())) { errorReporter.reportError(targetConfig.compiler + " failed with error code " + cMakeReturnCode); } @@ -137,7 +136,7 @@ public boolean runCCompiler( // For warnings (vs. errors), the return code is 0. // But we still want to mark the IDE. if (compile.getErrors().toString().length() > 0 && - context.getMode() != Mode.STANDALONE && + context.getMode() != LFGeneratorContext.Mode.STANDALONE && !outputContainsKnownCMakeErrors(compile.getErrors().toString())) { generator.reportCommandErrors(compile.getErrors().toString()); } @@ -150,7 +149,7 @@ public boolean runCCompiler( makeReturnCode = build.run(context.getCancelIndicator()); if (makeReturnCode != 0 && - context.getMode() == Mode.STANDALONE && + context.getMode() == LFGeneratorContext.Mode.STANDALONE && !outputContainsKnownCMakeErrors(build.getErrors().toString())) { errorReporter.reportError(targetConfig.compiler + " failed with error code " + makeReturnCode); } @@ -158,7 +157,7 @@ public boolean runCCompiler( // For warnings (vs. errors), the return code is 0. // But we still want to mark the IDE. if (build.getErrors().toString().length() > 0 && - context.getMode() != Mode.STANDALONE && + context.getMode() != LFGeneratorContext.Mode.STANDALONE && !outputContainsKnownCMakeErrors(build.getErrors().toString())) { generator.reportCommandErrors(build.getErrors().toString()); } diff --git a/org.lflang/src/org/lflang/generator/c/CCompiler.java b/org.lflang/src/org/lflang/generator/c/CCompiler.java index 66bab32aea..47efaeae8c 100644 --- a/org.lflang/src/org/lflang/generator/c/CCompiler.java +++ b/org.lflang/src/org/lflang/generator/c/CCompiler.java @@ -34,7 +34,6 @@ import org.lflang.ErrorReporter; import org.lflang.FileConfig; -import org.lflang.TargetConfig.Mode; import org.lflang.TargetConfig; import org.lflang.generator.GeneratorBase; import org.lflang.generator.GeneratorCommandFactory; @@ -122,7 +121,7 @@ public boolean runCCompiler( GeneratorBase generator, LFGeneratorContext context ) throws IOException { - if (noBinary && context.getMode() == Mode.STANDALONE) { + if (noBinary && context.getMode() == LFGeneratorContext.Mode.STANDALONE) { errorReporter.reportError("Did not output executable; no main reactor found."); } LFCommand compile = compileCCommand(file, noBinary); @@ -132,12 +131,12 @@ public boolean runCCompiler( int returnCode = compile.run(context.getCancelIndicator()); - if (returnCode != 0 && context.getMode() == Mode.STANDALONE) { + if (returnCode != 0 && context.getMode() == LFGeneratorContext.Mode.STANDALONE) { errorReporter.reportError(targetConfig.compiler+" returns error code "+returnCode); } // For warnings (vs. errors), the return code is 0. // But we still want to mark the IDE. - if (compile.getErrors().toString().length() > 0 && context.getMode() != Mode.STANDALONE) { + if (compile.getErrors().toString().length() > 0 && context.getMode() != LFGeneratorContext.Mode.STANDALONE) { generator.reportCommandErrors(compile.getErrors().toString()); } diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 61ee3e3b51..9ecfe58c60 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -45,7 +45,6 @@ import org.lflang.InferredType import org.lflang.JavaAstUtils import org.lflang.Target import org.lflang.TargetConfig -import org.lflang.TargetConfig.Mode import org.lflang.TargetProperty import org.lflang.TargetProperty.ClockSyncMode import org.lflang.TargetProperty.CoordinationType @@ -809,7 +808,7 @@ class CGenerator extends GeneratorBase { && targetConfig.buildCommands.nullOrEmpty && !federate.isRemote // This code is unreachable in LSP_FAST mode, so that check is omitted. - && context.getMode() != Mode.LSP_MEDIUM + && context.getMode() != LFGeneratorContext.Mode.LSP_MEDIUM ) { // FIXME: Currently, a lack of main is treated as a request to not produce // a binary and produce a .o file instead. There should be a way to control diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index 9e561cb8b6..ee107ee0b4 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -35,8 +35,8 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.TargetConfig; -import org.lflang.TargetConfig.Mode; import org.lflang.generator.GeneratorCommandFactory; +import org.lflang.generator.LFGeneratorContext; import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; @@ -570,7 +570,7 @@ public static void runBuildCommand( GeneratorCommandFactory commandFactory, ErrorReporter errorReporter, ReportCommandErrors reportCommandErrors, - TargetConfig.Mode mode + LFGeneratorContext.Mode mode ) { List commands = getCommands(targetConfig.buildCommands, commandFactory, fileConfig.srcPath); // If the build command could not be found, abort. @@ -579,7 +579,7 @@ public static void runBuildCommand( for (LFCommand cmd : commands) { int returnCode = cmd.run(); - if (returnCode != 0 && mode != Mode.EPOCH) { + if (returnCode != 0 && mode != LFGeneratorContext.Mode.EPOCH) { errorReporter.reportError(String.format( // FIXME: Why is the content of stderr not provided to the user in this error message? "Build command \"%s\" failed with error code %d.", @@ -589,7 +589,7 @@ public static void runBuildCommand( } // For warnings (vs. errors), the return code is 0. // But we still want to mark the IDE. - if (!cmd.getErrors().toString().isEmpty() && mode == Mode.EPOCH) { + if (!cmd.getErrors().toString().isEmpty() && mode == LFGeneratorContext.Mode.EPOCH) { reportCommandErrors.report(cmd.getErrors().toString()); return; // FIXME: Why do we return here? Even if there are warnings, the build process should proceed. } diff --git a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt index 8b86bb04d2..2ae7fc107a 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt @@ -29,7 +29,7 @@ package org.lflang.generator.cpp import org.eclipse.emf.ecore.resource.Resource import org.lflang.ErrorReporter import org.lflang.Target -import org.lflang.TargetConfig.Mode +import org.lflang.generator.LFGeneratorContext.Mode import org.lflang.TargetProperty import org.lflang.TimeUnit import org.lflang.TimeValue diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 2c911104a7..598c44c908 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -41,7 +41,6 @@ import org.lflang.FileConfig import org.lflang.InferredType import org.lflang.JavaAstUtils import org.lflang.Target -import org.lflang.TargetConfig.Mode import org.lflang.TargetProperty.CoordinationType import org.lflang.federated.FedFileConfig import org.lflang.federated.FederateInstance @@ -1350,7 +1349,7 @@ class PythonGenerator extends CGenerator { ) // If there are no federates, compile and install the generated code new PythonValidator(fileConfig, errorReporter, codeMaps, protoNames).doValidate(context) - if (!errorsOccurred() && context.mode != Mode.LSP_MEDIUM) { + if (!errorsOccurred() && context.mode != LFGeneratorContext.Mode.LSP_MEDIUM) { compilingFederatesContext.reportProgress( String.format("Validation complete. Compiling and installing %d/%d Python modules...", federateCount, federates.size()), diff --git a/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt b/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt index b2a100ecb0..283e5fa1a4 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt @@ -25,10 +25,8 @@ package org.lflang.generator.rust import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.xtext.generator.IFileSystemAccess2 import org.lflang.ErrorReporter import org.lflang.Target -import org.lflang.TargetConfig import org.lflang.TargetProperty.BuildType import org.lflang.generator.canGenerate import org.lflang.generator.CodeMap @@ -85,7 +83,7 @@ class RustGenerator( ) val exec = fileConfig.binPath.toAbsolutePath().resolve(gen.executableName) Files.deleteIfExists(exec) // cleanup, cargo doesn't do it - if (context.mode == TargetConfig.Mode.LSP_MEDIUM) RustValidator(fileConfig, errorReporter, codeMaps).doValidate(context) + if (context.mode == LFGeneratorContext.Mode.LSP_MEDIUM) RustValidator(fileConfig, errorReporter, codeMaps).doValidate(context) else invokeRustCompiler(context, gen.executableName, codeMaps) } } diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index 3fe2fd783d..ebe303eff5 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -28,27 +28,13 @@ package org.lflang.generator.ts import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.util.CancelIndicator import org.lflang.ErrorReporter -import org.lflang.inferredType import org.lflang.InferredType -import org.lflang.TimeValue import org.lflang.JavaAstUtils import org.lflang.Target -import org.lflang.TargetConfig.Mode -import org.lflang.federated.launcher.FedTSLauncher +import org.lflang.TimeValue import org.lflang.federated.FederateInstance -import org.lflang.lf.Action -import org.lflang.lf.Delay -import org.lflang.lf.Instantiation -import org.lflang.lf.Parameter -import org.lflang.lf.StateVar -import org.lflang.lf.Type -import org.lflang.lf.Value -import org.lflang.lf.VarRef -import org.lflang.scoping.LFGlobalScopeProvider -import java.nio.file.Files -import java.util.LinkedList +import org.lflang.federated.launcher.FedTSLauncher import org.lflang.federated.serialization.SupportedSerializers -import org.lflang.generator.canGenerate import org.lflang.generator.CodeMap import org.lflang.generator.GeneratorBase import org.lflang.generator.GeneratorResult @@ -56,12 +42,24 @@ import org.lflang.generator.IntegratedBuilder import org.lflang.generator.JavaGeneratorUtils import org.lflang.generator.LFGeneratorContext import org.lflang.generator.PrependOperator +import org.lflang.generator.SubContext import org.lflang.generator.TargetTypes import org.lflang.generator.ValueGenerator -import org.lflang.generator.SubContext +import org.lflang.generator.canGenerate +import org.lflang.inferredType +import org.lflang.lf.Action +import org.lflang.lf.Delay +import org.lflang.lf.Instantiation +import org.lflang.lf.Parameter +import org.lflang.lf.StateVar +import org.lflang.lf.Type +import org.lflang.lf.Value +import org.lflang.lf.VarRef +import org.lflang.scoping.LFGlobalScopeProvider +import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardCopyOption -import kotlin.collections.HashMap +import java.util.* private const val NO_NPM_MESSAGE = "The TypeScript target requires npm >= 6.14.4. " + "For installation instructions, see: https://www.npmjs.com/get-npm. \n" + @@ -186,7 +184,7 @@ class TSGenerator( !context.cancelIndicator.isCanceled && passesChecks(TSValidator(tsFileConfig, errorReporter, codeMaps), parsingContext) ) { - if (context.mode == Mode.LSP_MEDIUM) { + if (context.mode == LFGeneratorContext.Mode.LSP_MEDIUM) { context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(codeMaps)) } else { compile(resource, parsingContext) @@ -203,7 +201,7 @@ class TSGenerator( */ private fun clean(context: LFGeneratorContext) { // Dirty shortcut for integrated mode: Delete nothing, saving the node_modules directory to avoid re-running pnpm. - if (context.mode != Mode.LSP_MEDIUM) fileConfig.deleteDirectory(fileConfig.srcGenPath) + if (context.mode != LFGeneratorContext.Mode.LSP_MEDIUM) fileConfig.deleteDirectory(fileConfig.srcGenPath) } /** @@ -301,8 +299,8 @@ class TSGenerator( /** * Return whether it is advisable to install dependencies. */ - private fun shouldCollectDependencies(context: LFGeneratorContext): Boolean - = context.mode != Mode.LSP_MEDIUM || !fileConfig.srcGenPkgPath.resolve("node_modules").toFile().exists() + private fun shouldCollectDependencies(context: LFGeneratorContext): Boolean = + context.mode != LFGeneratorContext.Mode.LSP_MEDIUM || !fileConfig.srcGenPkgPath.resolve("node_modules").toFile().exists() /** * Collect the dependencies in package.json and their From 58f14274923746e3b8df6401cde6a7c1c94385d2 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 14:27:24 +0100 Subject: [PATCH 05/30] don't use fileConfig.context in code generators --- .../src/org/lflang/generator/GeneratorBase.xtend | 15 +++++++-------- .../src/org/lflang/generator/c/CGenerator.xtend | 4 ++-- .../lflang/generator/python/PythonGenerator.xtend | 7 ------- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index f5eb62079d..5bb99ea475 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -290,7 +290,7 @@ abstract class GeneratorBase extends AbstractLFValidator { cleanIfNeeded(context) - printInfo() + printInfo(context.mode) // Clear any IDE markers that may have been created by a previous build. // Markers mark problems in the Eclipse IDE when running in integrated mode. @@ -327,7 +327,7 @@ abstract class GeneratorBase extends AbstractLFValidator { // Collect reactors and create an instantiation graph. // These are needed to figure out which resources we need // to validate, which happens in setResources(). - setReactorsAndInstantiationGraph() + setReactorsAndInstantiationGraph(context.mode) JavaGeneratorUtils.validate(context, fileConfig, instantiationGraph, errorReporter) val allResources = JavaGeneratorUtils.getResources(reactors) @@ -346,7 +346,7 @@ abstract class GeneratorBase extends AbstractLFValidator { // Invoke these functions a second time because transformations // may have introduced new reactors! - setReactorsAndInstantiationGraph() + setReactorsAndInstantiationGraph(context.mode) enableSupportForSerializationIfApplicable(context.cancelIndicator); } @@ -373,7 +373,7 @@ abstract class GeneratorBase extends AbstractLFValidator { * Hence, after this method returns, `this.reactors` will be a list of Reactors such that any * reactor is preceded in the list by reactors that it instantiates. */ - protected def setReactorsAndInstantiationGraph() { + protected def setReactorsAndInstantiationGraph(LFGeneratorContext.Mode mode) { // Build the instantiation graph . this.instantiationGraph = new InstantiationGraph(fileConfig.resource, false) @@ -385,7 +385,7 @@ abstract class GeneratorBase extends AbstractLFValidator { // If there is no main reactor or if all reactors in the file need to be validated, then make sure the reactors // list includes even reactors that are not instantiated anywhere. - if (mainDef === null || fileConfig.context.mode == LFGeneratorContext.Mode.LSP_MEDIUM) { + if (mainDef === null || mode == LFGeneratorContext.Mode.LSP_MEDIUM) { for (r : fileConfig.resource.allContents.toIterable.filter(Reactor)) { if (!this.reactors.contains(r)) { this.reactors.add(r); @@ -1158,10 +1158,9 @@ abstract class GeneratorBase extends AbstractLFValidator { * Print to stdout information about what source file is being generated, * what mode the generator is in, and where the generated sources are to be put. */ - def printInfo() { + def printInfo(LFGeneratorContext.Mode mode) { println("Generating code for: " + fileConfig.resource.getURI.toString) - println('******** mode: ' + fileConfig.context.mode) - println('******** source file: ' + fileConfig.srcFile) // FIXME: redundant + println('******** mode: ' + mode) println('******** generated sources: ' + fileConfig.getSrcGenPath) } diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 9ecfe58c60..f9bd544eb6 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -329,8 +329,8 @@ class CGenerator extends GeneratorBase { //////////////////////////////////////////// //// Public methods - override printInfo() { - super.printInfo() + override printInfo(LFGeneratorContext.Mode mode) { + super.printInfo(mode) println('******** generated binaries: ' + fileConfig.binPath) } diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 598c44c908..61e9330214 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -172,13 +172,6 @@ class PythonGenerator extends CGenerator { val protoNames = new HashSet() // ////////////////////////////////////////// - // // Public methods - override printInfo() { - println("Generating code for: " + fileConfig.resource.getURI.toString) - println('******** Mode: ' + fileConfig.context.mode) - println('******** Generated sources: ' + fileConfig.getSrcGenPath) - } - /** * Print information about necessary steps to install the supporting * Python C extension for the generated program. From dea2128ba68986b6d1fc6ddc5b75fd6e0227a7c6 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 14:44:12 +0100 Subject: [PATCH 06/30] remove dependency on context from EclipseErrorReporter --- .../generator/EclipseErrorReporter.java | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/EclipseErrorReporter.java b/org.lflang/src/org/lflang/generator/EclipseErrorReporter.java index b7ad052258..ab2b877a61 100644 --- a/org.lflang/src/org/lflang/generator/EclipseErrorReporter.java +++ b/org.lflang/src/org/lflang/generator/EclipseErrorReporter.java @@ -41,7 +41,9 @@ /** * An error reporter that prints messages to the command line output and also - * sets markers in the Eclipse IDE if running in integrated mode. + * sets markers in the Epoch IDE. + * + * This class should only be used in EPOCH Mode. */ public class EclipseErrorReporter implements ErrorReporter { @@ -108,43 +110,39 @@ private String report(String message, Severity severity, Integer line, Path file System.err.println(header + ": " + file.toString() + " line " + line.toString() + "\n" + message); - // If running in EPOCH mode, create a marker in the IDE for the - // error. - // See: - // https://help.eclipse.org/2020-03/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Fguide%2FresAdv_markers.htm - if (fileConfig.context.getMode() == LFGeneratorContext.Mode.EPOCH) { - final IResource iResource = file != null - ? fileConfig.getIResource(file) - : fileConfig.iResource; - - try { - IMarker marker = iResource.createMarker(IMarker.PROBLEM); - - // Mark as LF compilation marker to be able to remove marker at next compile run - marker.setAttribute(this.getClass().getName(), true); - - marker.setAttribute(IMarker.MESSAGE, message); - marker.setAttribute(IMarker.LINE_NUMBER, - line == null ? 1 : line); - // Human-readable line number information. - marker.setAttribute(IMarker.LOCATION, - line == null ? "1" : line.toString()); - // Mark as an error or warning. - marker.setAttribute(IMarker.SEVERITY, isError ? IMarker.SEVERITY_ERROR : IMarker.SEVERITY_WARNING); - marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); - - marker.setAttribute(IMarker.USER_EDITABLE, false); - - } catch (CoreException e) { - // Ignore, but print a warning - System.err.println("WARNING: Setting markers in the IDE failed:\n" + e.toString()); - } + // Create a marker in the IDE for the error. + // See: https://help.eclipse.org/2020-03/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Fguide%2FresAdv_markers.htm + final IResource iResource = file != null + ? fileConfig.getIResource(file) + : fileConfig.iResource; + + try { + IMarker marker = iResource.createMarker(IMarker.PROBLEM); + + // Mark as LF compilation marker to be able to remove marker at next compile run + marker.setAttribute(this.getClass().getName(), true); - // NOTE: It might be useful to set a start and end. - // marker.setAttribute(IMarker.CHAR_START, 0); - // marker.setAttribute(IMarker.CHAR_END, 5); + marker.setAttribute(IMarker.MESSAGE, message); + marker.setAttribute(IMarker.LINE_NUMBER, + line == null ? 1 : line); + // Human-readable line number information. + marker.setAttribute(IMarker.LOCATION, + line == null ? "1" : line.toString()); + // Mark as an error or warning. + marker.setAttribute(IMarker.SEVERITY, isError ? IMarker.SEVERITY_ERROR : IMarker.SEVERITY_WARNING); + marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); + + marker.setAttribute(IMarker.USER_EDITABLE, false); + + } catch (CoreException e) { + // Ignore, but print a warning + System.err.println("WARNING: Setting markers in the IDE failed:\n" + e.toString()); } + // NOTE: It might be useful to set a start and end. + // marker.setAttribute(IMarker.CHAR_START, 0); + // marker.setAttribute(IMarker.CHAR_END, 5); + // Return a string that can be inserted into the generated code. return header + ": " + message; } From ce23d5be1ecfce4a701746ae4d3888e97612fdd5 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 14:59:06 +0100 Subject: [PATCH 07/30] remove context from FileConfig --- org.lflang/src/org/lflang/FileConfig.java | 33 +++++++------------ .../org/lflang/federated/FedFileConfig.java | 2 +- .../lflang/generator/JavaGeneratorUtils.java | 2 +- .../src/org/lflang/generator/LFGenerator.java | 4 +-- .../org/lflang/generator/cpp/CppFileConfig.kt | 4 +-- .../lflang/generator/rust/RustFileConfig.kt | 4 +-- .../org/lflang/generator/ts/TSFileConfig.kt | 4 +-- 7 files changed, 22 insertions(+), 31 deletions(-) diff --git a/org.lflang/src/org/lflang/FileConfig.java b/org.lflang/src/org/lflang/FileConfig.java index 14e645526a..2a67883704 100644 --- a/org.lflang/src/org/lflang/FileConfig.java +++ b/org.lflang/src/org/lflang/FileConfig.java @@ -36,7 +36,6 @@ import org.eclipse.xtext.generator.IFileSystemAccess2; import org.eclipse.xtext.util.RuntimeIOException; -import org.lflang.generator.LFGeneratorContext; import org.lflang.lf.Reactor; /** @@ -81,14 +80,6 @@ public class FileConfig { */ public final Path binPath; - /** - * Object used for communication between the IDE or stand-alone compiler - * and the code generator. - */ - // FIXME: Delete this field? It is used, but it seems out of place, especially given that many methods where a - // FileConfig is used also have access to the context (or their callers have access to the context) - public final LFGeneratorContext context; - /** * The name of the main reactor, which has to match the file name (without * the '.lf' extension). @@ -131,6 +122,11 @@ public class FileConfig { */ public final Path srcPath; + /** + * Indicate whether the bin directory should be hierarchical. + */ + public final boolean useHierarchicalBin; + // Protected fields. /** @@ -148,6 +144,7 @@ public class FileConfig { */ protected Path srcGenPath; + // private fields /** @@ -169,10 +166,9 @@ public class FileConfig { */ private final Path srcGenPkgPath; - - public FileConfig(Resource resource, Path srcGenBasePath, LFGeneratorContext context) throws IOException { + public FileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchicalBin) throws IOException { this.resource = resource; - this.context = context; + this.useHierarchicalBin = useHierarchicalBin; this.srcFile = toPath(this.resource); @@ -184,7 +180,10 @@ public FileConfig(Resource resource, Path srcGenBasePath, LFGeneratorContext con this.srcGenPath = srcGenBasePath.resolve(getSubPkgPath(srcPkgPath, srcPath)).resolve(name); this.srcGenPkgPath = this.srcGenPath; this.outPath = srcGenBasePath.getParent(); - this.binPath = getBinPath(this.srcPkgPath, this.srcPath, this.outPath, context); + + Path binRoot = outPath.resolve(DEFAULT_BIN_DIR); + this.binPath = useHierarchicalBin ? binRoot.resolve(getSubPkgPath(srcPkgPath, srcPath)) : binRoot; + this.iResource = getIResource(resource); } @@ -316,14 +315,6 @@ public Path getRTIBinPath() { return this.binPath; } - /** - * Return the output directory for generated binary files. - */ - private static Path getBinPath(Path pkgPath, Path srcPath, Path outPath, LFGeneratorContext context) { - Path root = outPath.resolve(DEFAULT_BIN_DIR); - return context.useHierarchicalBin() ? root.resolve(getSubPkgPath(pkgPath, srcPath)) : root; - } - /** * Returns the root directory for generated sources. */ diff --git a/org.lflang/src/org/lflang/federated/FedFileConfig.java b/org.lflang/src/org/lflang/federated/FedFileConfig.java index 14baea8e66..b6bd0c61b6 100644 --- a/org.lflang/src/org/lflang/federated/FedFileConfig.java +++ b/org.lflang/src/org/lflang/federated/FedFileConfig.java @@ -51,7 +51,7 @@ public class FedFileConfig extends FileConfig { * @throws IOException */ public FedFileConfig(final FileConfig fileConfig, final String federateName) throws IOException { - super(fileConfig.resource, fileConfig.getSrcGenBasePath(), fileConfig.context); + super(fileConfig.resource, fileConfig.getSrcGenBasePath(), fileConfig.useHierarchicalBin); this.federateName = federateName; // The generated code for each federate should be located at fileConfig.srcGenPath + "/federateName/" diff --git a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java index 97414cf5e3..b29836eb8a 100644 --- a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java +++ b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java @@ -296,7 +296,7 @@ public static LFResource getLFResource( } FileConfig fc; try { - fc = new FileConfig(resource, srcGenBasePath, context); + fc = new FileConfig(resource, srcGenBasePath, context.useHierarchicalBin()); } catch (IOException e) { throw new RuntimeException("Failed to instantiate an imported resource because an I/O error " + "occurred."); diff --git a/org.lflang/src/org/lflang/generator/LFGenerator.java b/org.lflang/src/org/lflang/generator/LFGenerator.java index fc7a74c154..ef710d2138 100644 --- a/org.lflang/src/org/lflang/generator/LFGenerator.java +++ b/org.lflang/src/org/lflang/generator/LFGenerator.java @@ -72,10 +72,10 @@ private FileConfig createFileConfig(final Target target, } catch (InvocationTargetException e) { throw new RuntimeException("Exception instantiating " + className, e.getTargetException()); } catch (ReflectiveOperationException e) { - return new FileConfig(resource, srcGenBasePath, context); + return new FileConfig(resource, srcGenBasePath, context.useHierarchicalBin()); } default: - return new FileConfig(resource, srcGenBasePath, context); + return new FileConfig(resource, srcGenBasePath, context.useHierarchicalBin()); } } diff --git a/org.lflang/src/org/lflang/generator/cpp/CppFileConfig.kt b/org.lflang/src/org/lflang/generator/cpp/CppFileConfig.kt index 56d131d5b5..0300da0d00 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppFileConfig.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppFileConfig.kt @@ -34,8 +34,8 @@ import org.lflang.name import java.io.IOException import java.nio.file.Path -class CppFileConfig(resource: Resource, srcGenBasePath: Path, context: LFGeneratorContext) : - FileConfig(resource, srcGenBasePath, context) { +class CppFileConfig(resource: Resource, srcGenBasePath: Path, useHierarchicalBin: Boolean) : + FileConfig(resource, srcGenBasePath, useHierarchicalBin) { /** * Clean any artifacts produced by the C++ code generator. diff --git a/org.lflang/src/org/lflang/generator/rust/RustFileConfig.kt b/org.lflang/src/org/lflang/generator/rust/RustFileConfig.kt index c44766f8f2..4545dbd4b1 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustFileConfig.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustFileConfig.kt @@ -34,8 +34,8 @@ import java.nio.file.Files import java.nio.file.Path import kotlin.system.measureTimeMillis -class RustFileConfig(resource: Resource, srcGenBasePath: Path, context: LFGeneratorContext) : - FileConfig(resource, srcGenBasePath, context) { +class RustFileConfig(resource: Resource, srcGenBasePath: Path, useHierarchicalBin: Boolean) : + FileConfig(resource, srcGenBasePath, useHierarchicalBin) { /** * Clean any artifacts produced by the C++ code generator. diff --git a/org.lflang/src/org/lflang/generator/ts/TSFileConfig.kt b/org.lflang/src/org/lflang/generator/ts/TSFileConfig.kt index 5a0fd7a01b..6ff1940916 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSFileConfig.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSFileConfig.kt @@ -41,8 +41,8 @@ import java.nio.file.Path * @author {Hokeun Kim } */ class TSFileConfig( - resource: Resource, srcGenBasePath: Path, context: LFGeneratorContext -) : FileConfig(resource, srcGenBasePath, context) { + resource: Resource, srcGenBasePath: Path, useHierarchicalBin: Boolean +) : FileConfig(resource, srcGenBasePath, useHierarchicalBin) { /** * Clean any artifacts produced by the TypeScript code generator. From b7c27ca9d21fb40fb481232a6b5f6edeae5be457 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 15:08:09 +0100 Subject: [PATCH 08/30] fix test code --- .../src/org/lflang/tests/Configurators.java | 4 ++-- org.lflang.tests/src/org/lflang/tests/LFTest.java | 11 +++-------- org.lflang.tests/src/org/lflang/tests/TestBase.java | 10 +++++----- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/Configurators.java b/org.lflang.tests/src/org/lflang/tests/Configurators.java index 872a4341b7..97f91616ac 100644 --- a/org.lflang.tests/src/org/lflang/tests/Configurators.java +++ b/org.lflang.tests/src/org/lflang/tests/Configurators.java @@ -52,7 +52,7 @@ public interface Configurator { * @return True if successful, false otherwise. */ static boolean useSingleThread(LFTest test) { - test.getContext().getArgs().setProperty("threads", "0"); + test.context.getArgs().setProperty("threads", "0"); return true; } @@ -63,7 +63,7 @@ static boolean useSingleThread(LFTest test) { * @return True if successful, false otherwise. */ static boolean useFourThreads(LFTest test) { - test.getContext().getArgs().setProperty("threads", "4"); + test.context.getArgs().setProperty("threads", "4"); return true; } diff --git a/org.lflang.tests/src/org/lflang/tests/LFTest.java b/org.lflang.tests/src/org/lflang/tests/LFTest.java index 64a6691fca..ba096315ee 100644 --- a/org.lflang.tests/src/org/lflang/tests/LFTest.java +++ b/org.lflang.tests/src/org/lflang/tests/LFTest.java @@ -35,6 +35,9 @@ public class LFTest implements Comparable { /** Object used to determine where the code generator puts files. */ public FileConfig fileConfig; + /** Context provided to the code generators */ + public LFGeneratorContext context; + /** Path of the test program relative to the package root. */ private final Path relativePath; @@ -113,14 +116,6 @@ public boolean hasFailed() { return result != Result.TEST_PASS; } - /** - * Return the context stored in this test's file configuration. - * @return The context for this test, to be passed to the code generator. - */ - public LFGeneratorContext getContext() { - return this.fileConfig.context; - } - /** * Compile a string that contains all collected errors and return it. * @return A string that contains all collected errors. diff --git a/org.lflang.tests/src/org/lflang/tests/TestBase.java b/org.lflang.tests/src/org/lflang/tests/TestBase.java index 5a2263e72d..03dbfcfea8 100644 --- a/org.lflang.tests/src/org/lflang/tests/TestBase.java +++ b/org.lflang.tests/src/org/lflang/tests/TestBase.java @@ -45,7 +45,6 @@ import org.lflang.LFRuntimeModule; import org.lflang.LFStandaloneSetup; import org.lflang.Target; -import org.lflang.TargetConfig.Mode; import org.lflang.generator.GeneratorResult; import org.lflang.generator.LFGenerator; import org.lflang.generator.LFGeneratorContext; @@ -365,7 +364,7 @@ private static void checkAndReportFailures(Set tests) { */ private LFGeneratorContext configure(LFTest test, Configurator configurator, TestLevel level) throws IOException { var context = new MainContext( - Mode.STANDALONE, CancelIndicator.NullImpl, (m, p) -> {}, new Properties(), true, + LFGeneratorContext.Mode.STANDALONE, CancelIndicator.NullImpl, (m, p) -> {}, new Properties(), true, fileConfig -> new DefaultErrorReporter() ); @@ -379,7 +378,8 @@ private LFGeneratorContext configure(LFTest test, Configurator configurator, Tes } fileAccess.setOutputPath(FileConfig.findPackageRoot(test.srcFile, s -> {}).resolve(FileConfig.DEFAULT_SRC_GEN_DIR).toString()); - test.fileConfig = new FileConfig(r, FileConfig.getSrcGenRoot(fileAccess), context); + test.context = context; + test.fileConfig = new FileConfig(r, FileConfig.getSrcGenRoot(fileAccess), context.useHierarchicalBin()); // Set the no-compile flag the test is not supposed to reach the build stage. if (level.compareTo(TestLevel.BUILD) < 0) { @@ -436,8 +436,8 @@ protected void addExtraLfcArgs(Properties args) { private GeneratorResult generateCode(LFTest test) { GeneratorResult result = GeneratorResult.NOTHING; if (test.fileConfig.resource != null) { - generator.doGenerate(test.fileConfig.resource, fileAccess, test.fileConfig.context); - result = test.fileConfig.context.getResult(); + generator.doGenerate(test.fileConfig.resource, fileAccess, test.context); + result = test.context.getResult(); if (generator.errorsOccurred()) { test.result = Result.CODE_GEN_FAIL; throw new AssertionError("Code generation unsuccessful."); From 2ca537e191db2093f9c286010e41fec20a8a4e9a Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 15:35:07 +0100 Subject: [PATCH 09/30] delete unused functions --- org.lflang/src/org/lflang/FileConfig.java | 26 +---------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/org.lflang/src/org/lflang/FileConfig.java b/org.lflang/src/org/lflang/FileConfig.java index 2a67883704..167ea2c984 100644 --- a/org.lflang/src/org/lflang/FileConfig.java +++ b/org.lflang/src/org/lflang/FileConfig.java @@ -298,22 +298,6 @@ public Path getSrcGenBasePath() { public Path getSrcGenPkgPath() { return srcGenPkgPath; } - - /** - * Return the directory in which to put the generated sources for the - * RTI. By default, this is the same as the regular src-gen directory. - */ - public Path getRTISrcPath() { - return this.srcGenPath; - } - - /** - * Return the directory in which to put the generated binaries for the - * RTI. By default, this is the same as the regular src-gen directory. - */ - public Path getRTIBinPath() { - return this.binPath; - } /** * Returns the root directory for generated sources. @@ -662,7 +646,7 @@ public void doClean() throws IOException { deleteDirectory(binPath); deleteDirectory(srcGenBasePath); } - + /** * Remove files in the bin directory that may have been created. * Call this if a compilation occurs so that files from a previous @@ -837,14 +821,6 @@ public String getRTIBinName() { return nameWithoutExtension(srcFile) + RTI_BIN_SUFFIX; } - /** - * Return the file location of the RTI executable. - * @return The file location of the RTI executable. - */ - public File getRTIBinFile() { - return this.binPath.resolve(getRTIBinName()).toFile(); - } - /** * Return the name of the RTI distribution script. * @return The name of the RTI distribution script. From 462e062fd6ad7b3dba3e37382160ec5aeb11c9f8 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 15:41:30 +0100 Subject: [PATCH 10/30] avoid federation specific code in FileConfig --- org.lflang/src/org/lflang/FileConfig.java | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/org.lflang/src/org/lflang/FileConfig.java b/org.lflang/src/org/lflang/FileConfig.java index 167ea2c984..c8eb292149 100644 --- a/org.lflang/src/org/lflang/FileConfig.java +++ b/org.lflang/src/org/lflang/FileConfig.java @@ -669,8 +669,8 @@ public void deleteBinFiles() { // Delete executable file or launcher script, if any. // Delete distribution file, if any. // Delete RTI file, if any. - if (f.equals(name) || f.equals(getRTIBinName()) - || f.equals(getRTIDistributionScriptName())) { + if (f.equals(name) || f.equals(name + RTI_BIN_SUFFIX) + || f.equals(name + RTI_DISTRIBUTION_SCRIPT_SUFFIX)) { //noinspection ResultOfMethodCallIgnored this.binPath.resolve(f).toFile().delete(); } @@ -813,22 +813,6 @@ public static Path findFile(String fileName, Path directory) { return null; } - /** - * Return the name of the RTI executable. - * @return The name of the RTI executable. - */ - public String getRTIBinName() { - return nameWithoutExtension(srcFile) + RTI_BIN_SUFFIX; - } - - /** - * Return the name of the RTI distribution script. - * @return The name of the RTI distribution script. - */ - public String getRTIDistributionScriptName() { - return nameWithoutExtension(srcFile) + RTI_DISTRIBUTION_SCRIPT_SUFFIX; - } - /** * Return the name of the file associated with the given resource, * excluding its file extension. From f5e6d32c9d37d13c42a6a7568866c0377e2598c3 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 15:45:39 +0100 Subject: [PATCH 11/30] move C-specific function deleteBinFiles() to CUtil --- org.lflang/src/org/lflang/FileConfig.java | 50 ----------------- .../org/lflang/generator/c/CGenerator.xtend | 2 +- .../src/org/lflang/generator/c/CUtil.java | 53 +++++++++++++++++++ 3 files changed, 54 insertions(+), 51 deletions(-) diff --git a/org.lflang/src/org/lflang/FileConfig.java b/org.lflang/src/org/lflang/FileConfig.java index c8eb292149..896e9b1fbd 100644 --- a/org.lflang/src/org/lflang/FileConfig.java +++ b/org.lflang/src/org/lflang/FileConfig.java @@ -60,19 +60,6 @@ public class FileConfig { */ public static final String DEFAULT_SRC_GEN_DIR = "src-gen"; - /** - * Suffix that when appended to the name of a federated reactor yields - * the name of its corresponding RTI executable. - */ - public static final String RTI_BIN_SUFFIX = "_RTI"; - - /** - * Suffix that when appended to the name of a federated reactor yields - * the name of its corresponding distribution script. - */ - public static final String RTI_DISTRIBUTION_SCRIPT_SUFFIX = "_distribute.sh"; - - // Public fields. /** @@ -646,43 +633,6 @@ public void doClean() throws IOException { deleteDirectory(binPath); deleteDirectory(srcGenBasePath); } - - /** - * Remove files in the bin directory that may have been created. - * Call this if a compilation occurs so that files from a previous - * version do not accidentally get executed. - */ - public void deleteBinFiles() { - String name = nameWithoutExtension(this.srcFile); - String[] files = this.binPath.toFile().list(); - List federateNames = new LinkedList<>(); // FIXME: put this in ASTUtils? - resource.getAllContents().forEachRemaining(node -> { - if (node instanceof Reactor) { - Reactor r = (Reactor) node; - if (r.isFederated()) { - r.getInstantiations().forEach(inst -> federateNames - .add(inst.getName())); - } - } - }); - for (String f : files) { - // Delete executable file or launcher script, if any. - // Delete distribution file, if any. - // Delete RTI file, if any. - if (f.equals(name) || f.equals(name + RTI_BIN_SUFFIX) - || f.equals(name + RTI_DISTRIBUTION_SCRIPT_SUFFIX)) { - //noinspection ResultOfMethodCallIgnored - this.binPath.resolve(f).toFile().delete(); - } - // Delete federate executable files, if any. - for (String federateName : federateNames) { - if (f.equals(name + "_" + federateName)) { - //noinspection ResultOfMethodCallIgnored - this.binPath.resolve(f).toFile().delete(); - } - } - } - } public static String nameWithoutExtension(Path file) { String name = file.getFileName().toString(); diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index f9bd544eb6..050651b7b8 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -837,7 +837,7 @@ class CGenerator extends GeneratorBase { } if (!cCompiler.runCCompiler(execName, main === null, generator, context)) { // If compilation failed, remove any bin files that may have been created. - threadFileConfig.deleteBinFiles() + CUtil.deleteBinFiles(threadFileConfig) // If finish has already been called, it is illegal and makes no sense. However, // if finish has already been called, then this must be a federated execution. if (!isFederated) context.unsuccessfulFinish(); diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index ee107ee0b4..e19b22642d 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -28,6 +28,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.nio.file.Path; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -43,6 +44,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.TriggerInstance; import org.lflang.lf.Parameter; import org.lflang.lf.Port; +import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; @@ -57,6 +59,18 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY */ public class CUtil { + /** + * Suffix that when appended to the name of a federated reactor yields + * the name of its corresponding RTI executable. + */ + public static final String RTI_BIN_SUFFIX = "_RTI"; + + /** + * Suffix that when appended to the name of a federated reactor yields + * the name of its corresponding distribution script. + */ + public static final String RTI_DISTRIBUTION_SCRIPT_SUFFIX = "_distribute.sh"; + ////////////////////////////////////////////////////// //// Public methods. @@ -596,6 +610,45 @@ public static void runBuildCommand( } } + + /** + * Remove files in the bin directory that may have been created. + * Call this if a compilation occurs so that files from a previous + * version do not accidentally get executed. + * @param fileConfig + */ + public static void deleteBinFiles(FileConfig fileConfig) { + String name = FileConfig.nameWithoutExtension(fileConfig.srcFile); + String[] files = fileConfig.binPath.toFile().list(); + List federateNames = new LinkedList<>(); // FIXME: put this in ASTUtils? + fileConfig.resource.getAllContents().forEachRemaining(node -> { + if (node instanceof Reactor) { + Reactor r = (Reactor) node; + if (r.isFederated()) { + r.getInstantiations().forEach(inst -> federateNames + .add(inst.getName())); + } + } + }); + for (String f : files) { + // Delete executable file or launcher script, if any. + // Delete distribution file, if any. + // Delete RTI file, if any. + if (f.equals(name) || f.equals(name + RTI_BIN_SUFFIX) + || f.equals(name + RTI_DISTRIBUTION_SCRIPT_SUFFIX)) { + //noinspection ResultOfMethodCallIgnored + fileConfig.binPath.resolve(f).toFile().delete(); + } + // Delete federate executable files, if any. + for (String federateName : federateNames) { + if (f.equals(name + "_" + federateName)) { + //noinspection ResultOfMethodCallIgnored + fileConfig.binPath.resolve(f).toFile().delete(); + } + } + } + } + ////////////////////////////////////////////////////// //// Private functions. From d93657e96cfcf45a1d4bcfb2aef1a63c4fec0063 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 16:02:16 +0100 Subject: [PATCH 12/30] bugfix --- org.lflang/src/org/lflang/generator/LFGenerator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/LFGenerator.java b/org.lflang/src/org/lflang/generator/LFGenerator.java index ef710d2138..ac0c462c84 100644 --- a/org.lflang/src/org/lflang/generator/LFGenerator.java +++ b/org.lflang/src/org/lflang/generator/LFGenerator.java @@ -67,8 +67,8 @@ private FileConfig createFileConfig(final Target target, String className = "org.lflang.generator." + target.packageName + "." + target.classNamePrefix + "FileConfig"; try { return (FileConfig) Class.forName(className) - .getDeclaredConstructor(Resource.class, Path.class, LFGeneratorContext.class) - .newInstance(resource, srcGenBasePath, context); + .getDeclaredConstructor(Resource.class, Path.class, boolean.class) + .newInstance(resource, srcGenBasePath, context.useHierarchicalBin()); } catch (InvocationTargetException e) { throw new RuntimeException("Exception instantiating " + className, e.getTargetException()); } catch (ReflectiveOperationException e) { From 3610b66b04c362fbf560953bd5f0f43aa0aba3b7 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 16:05:05 +0100 Subject: [PATCH 13/30] Create new class FileUtil and move some methods from FileConfig --- org.lflang.lfc/src/org/lflang/lfc/Main.java | 3 +- .../lflang/lfc/StandaloneIssueAcceptor.java | 4 +- org.lflang/src/org/lflang/FileConfig.java | 86 +++---------------- .../src/org/lflang/FileConfigExtensions.kt | 5 +- .../src/org/lflang/MainConflictChecker.java | 3 +- org.lflang/src/org/lflang/TargetProperty.java | 4 +- .../generator/EclipseErrorReporter.java | 3 +- .../lflang/generator/c/CCmakeCompiler.java | 9 +- .../lflang/generator/c/CCmakeGenerator.java | 3 +- .../src/org/lflang/generator/c/CCompiler.java | 9 +- .../org/lflang/generator/c/CGenerator.xtend | 3 +- .../src/org/lflang/generator/c/CUtil.java | 3 +- .../org/lflang/generator/ts/TSGenerator.kt | 3 +- org.lflang/src/org/lflang/util/FileUtil.java | 81 +++++++++++++++++ .../org/lflang/validation/LFValidator.java | 6 +- 15 files changed, 124 insertions(+), 101 deletions(-) create mode 100644 org.lflang/src/org/lflang/util/FileUtil.java diff --git a/org.lflang.lfc/src/org/lflang/lfc/Main.java b/org.lflang.lfc/src/org/lflang/lfc/Main.java index cce1c0d6b2..fd46f1e416 100644 --- a/org.lflang.lfc/src/org/lflang/lfc/Main.java +++ b/org.lflang.lfc/src/org/lflang/lfc/Main.java @@ -37,6 +37,7 @@ import org.lflang.LFStandaloneSetup; import org.lflang.generator.LFGeneratorContext; import org.lflang.generator.MainContext; +import org.lflang.util.FileUtil; import com.google.inject.Inject; import com.google.inject.Injector; @@ -332,7 +333,7 @@ public Resource getValidatedResource(Path path) { issueCollector.accept(new LfIssue(issue.getMessage(), issue.getSeverity(), issue.getLineNumber(), issue.getColumn(), issue.getLineNumberEnd(), issue.getColumnEnd(), - issue.getLength(), FileConfig.toPath(uri))); + issue.getLength(), FileUtil.toPath(uri))); } catch (IOException e) { reporter.printError("Unable to convert '" + uri + "' to path." + e); } diff --git a/org.lflang.lfc/src/org/lflang/lfc/StandaloneIssueAcceptor.java b/org.lflang.lfc/src/org/lflang/lfc/StandaloneIssueAcceptor.java index 14d81d72bc..3e5e3965eb 100644 --- a/org.lflang.lfc/src/org/lflang/lfc/StandaloneIssueAcceptor.java +++ b/org.lflang.lfc/src/org/lflang/lfc/StandaloneIssueAcceptor.java @@ -9,7 +9,7 @@ import org.eclipse.xtext.validation.EObjectDiagnosticImpl; import org.eclipse.xtext.validation.ValidationMessageAcceptor; -import org.lflang.FileConfig; +import org.lflang.util.FileUtil; import com.google.inject.Inject; @@ -57,7 +57,7 @@ void accept(Severity severity, String message, EObject object, EStructuralFeatur private Path getPath(EObjectDiagnosticImpl diagnostic) { Path file = null; try { - file = FileConfig.toPath(diagnostic.getUriToProblem()); + file = FileUtil.toPath(diagnostic.getUriToProblem()); } catch (IOException e) { // just continue with null } diff --git a/org.lflang/src/org/lflang/FileConfig.java b/org.lflang/src/org/lflang/FileConfig.java index 896e9b1fbd..36bb59e196 100644 --- a/org.lflang/src/org/lflang/FileConfig.java +++ b/org.lflang/src/org/lflang/FileConfig.java @@ -16,7 +16,6 @@ import java.util.Arrays; import java.util.Comparator; import java.util.Enumeration; -import java.util.LinkedList; import java.util.List; import java.util.function.Consumer; import java.util.jar.JarEntry; @@ -30,13 +29,12 @@ import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.FileLocator; -import org.eclipse.core.runtime.IPath; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.IFileSystemAccess2; import org.eclipse.xtext.util.RuntimeIOException; -import org.lflang.lf.Reactor; +import org.lflang.util.FileUtil; /** * Base class that governs the interactions between code generators and the file system. @@ -157,13 +155,13 @@ public FileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchica this.resource = resource; this.useHierarchicalBin = useHierarchicalBin; - this.srcFile = toPath(this.resource); + this.srcFile = FileUtil.toPath(this.resource); this.srcPath = srcFile.getParent(); this.srcPkgPath = getPkgPath(resource); this.srcGenBasePath = srcGenBasePath; - this.name = nameWithoutExtension(this.srcFile); + this.name = FileUtil.nameWithoutExtension(this.srcFile); this.srcGenPath = srcGenBasePath.resolve(getSubPkgPath(srcPkgPath, srcPath)).resolve(name); this.srcGenPkgPath = this.srcGenPath; this.outPath = srcGenBasePath.getParent(); @@ -181,7 +179,7 @@ public FileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchica */ public IResource getIResource(Resource r) throws IOException { IResource iResource = null; - java.net.URI uri = toPath(r).toFile().toURI(); + java.net.URI uri = FileUtil.toPath(r).toFile().toURI(); if (r.getURI().isPlatform()) { IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); IFile[] files = workspaceRoot.findFilesForLocationURI(uri); @@ -232,14 +230,14 @@ public IResource getIResource(java.net.URI uri) { * Get the file name of a resource without file extension */ public static String getName(Resource r) throws IOException { - return nameWithoutExtension(toPath(r)); + return FileUtil.nameWithoutExtension(FileUtil.toPath(r)); } /** * Get the directory a resource is located in relative to the root package */ public Path getDirectory(Resource r) throws IOException { - return getSubPkgPath(this.srcPkgPath, toPath(r).getParent()); + return getSubPkgPath(this.srcPkgPath, FileUtil.toPath(r).getParent()); } /** @@ -294,7 +292,7 @@ public static Path getSrcGenRoot(IFileSystemAccess2 fsa) throws IOException { if (srcGenURI.hasTrailingPathSeparator()) { srcGenURI = srcGenURI.trimSegments(1); } - return FileConfig.toPath(srcGenURI); + return FileUtil.toPath(srcGenURI); } /** @@ -608,7 +606,7 @@ public String copyFileOrResource(String fileName, Path srcDir, Path dstDir) { * * @throws IOException If an I/O error occurs. */ - public void deleteDirectory(Path dir) throws IOException { + public static void deleteDirectory(Path dir) throws IOException { if (Files.isDirectory(dir)) { System.out.println("Cleaning " + dir); List pathsToDelete = Files.walk(dir) @@ -633,17 +631,11 @@ public void doClean() throws IOException { deleteDirectory(binPath); deleteDirectory(srcGenBasePath); } - - public static String nameWithoutExtension(Path file) { - String name = file.getFileName().toString(); - int idx = name.lastIndexOf('.'); - return idx < 0 ? name : name.substring(0, idx); - } - + private static Path getPkgPath(Resource resource) throws IOException { if (resource.getURI().isPlatform()) { // We are in the RCA. - File srcFile = toPath(resource).toFile(); + File srcFile = FileUtil.toPath(resource).toFile(); for (IProject r : ResourcesPlugin.getWorkspace().getRoot().getProjects()) { Path p = Paths.get(r.getLocation().toFile().getAbsolutePath()); Path f = Paths.get(srcFile.getAbsolutePath()); @@ -652,7 +644,7 @@ private static Path getPkgPath(Resource resource) throws IOException { } } } - return findPackageRoot(toPath(resource), s -> {}); + return findPackageRoot(FileUtil.toPath(resource), s -> {}); } /** @@ -677,51 +669,6 @@ public static Path findPackageRoot(final Path input, final Consumer prin } while (!p.toFile().getName().equals("src")); return p.getParent(); } - - /** - * Return a java.nio.Path object corresponding to the given URI. - * @throws IOException If the given URI is invalid. - */ - public static Path toPath(URI uri) throws IOException { - return Paths.get(toIPath(uri).toFile().getAbsolutePath()); - } - - /** - * Return a java.nio.Path object corresponding to the given Resource. - * @throws IOException If the given resource has an invalid URI. - */ - public static Path toPath(Resource resource) throws IOException { - return FileConfig.toPath(resource.getURI()); - } - - /** - * Return an org.eclipse.core.runtime.Path object corresponding to the - * given URI. - * @throws IOException If the given URI is invalid. - */ - public static IPath toIPath(URI uri) throws IOException { - if (uri.isPlatform()) { - IPath path = new org.eclipse.core.runtime.Path(uri.toPlatformString(true)); - if (path.segmentCount() == 1) { - return ResourcesPlugin.getWorkspace().getRoot().getProject(path.lastSegment()).getLocation(); - } else { - return ResourcesPlugin.getWorkspace().getRoot().getFile(path).getLocation(); - } - } else if (uri.isFile()) { - return new org.eclipse.core.runtime.Path(uri.toFileString()); - } else { - throw new IOException("Unrecognized file protocol in URI " + uri); - } - } - - /** - * Convert a given path to a unix-style string. - * - * This ensures that '/' is used instead of '\' as file separator. - */ - public static String toUnixString(Path path) { - return path.toString().replace('\\', '/'); - } /** * Search for a given file name in the given directory. @@ -763,15 +710,4 @@ public static Path findFile(String fileName, Path directory) { return null; } - /** - * Return the name of the file associated with the given resource, - * excluding its file extension. - * @param r Any {@code Resource}. - * @return The name of the file associated with the given resource, - * excluding its file extension. - * @throws IOException If the resource has an invalid URI. - */ - public static String nameWithoutExtension(Resource r) throws IOException { - return nameWithoutExtension(toPath(r)); - } } diff --git a/org.lflang/src/org/lflang/FileConfigExtensions.kt b/org.lflang/src/org/lflang/FileConfigExtensions.kt index d89abb0089..6283c5881e 100644 --- a/org.lflang/src/org/lflang/FileConfigExtensions.kt +++ b/org.lflang/src/org/lflang/FileConfigExtensions.kt @@ -25,6 +25,7 @@ package org.lflang import org.eclipse.emf.ecore.resource.Resource +import org.lflang.util.FileUtil import java.nio.file.Path /** @@ -33,12 +34,12 @@ import java.nio.file.Path val Resource.name: String get() = FileConfig.getName(this) /** Get the path of the receiving resource */ -fun Resource.toPath() = FileConfig.toPath(this) +fun Resource.toPath() = FileUtil.toPath(this) /** * Convert a given path to a unix-style string. * * This ensures that '/' is used instead of '\' as file separator. */ -fun Path.toUnixString(): String = FileConfig.toUnixString(this) +fun Path.toUnixString(): String = FileUtil.toUnixString(this) diff --git a/org.lflang/src/org/lflang/MainConflictChecker.java b/org.lflang/src/org/lflang/MainConflictChecker.java index a15bcbabdc..ce43b9b154 100644 --- a/org.lflang/src/org/lflang/MainConflictChecker.java +++ b/org.lflang/src/org/lflang/MainConflictChecker.java @@ -18,6 +18,7 @@ import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.xtext.xbase.lib.IteratorExtensions; import org.lflang.lf.Reactor; +import org.lflang.util.FileUtil; import com.google.common.collect.Iterables; @@ -98,7 +99,7 @@ public FileVisitResult visitFile(Path path, BasicFileAttributes attr) { // and the name matches, then report the conflict. if (!fileConfig.srcFile.equals(path) && IteratorExtensions.exists(reactors, it -> it.isMain() || it.isFederated()) - && fileConfig.name.equals(FileConfig.nameWithoutExtension(path))) { + && fileConfig.name.equals(FileUtil.nameWithoutExtension(path))) { conflicts.add( fileConfig.srcPath.relativize(path).toString()); } diff --git a/org.lflang/src/org/lflang/TargetProperty.java b/org.lflang/src/org/lflang/TargetProperty.java index 7307470af2..0b8ff64cf2 100644 --- a/org.lflang/src/org/lflang/TargetProperty.java +++ b/org.lflang/src/org/lflang/TargetProperty.java @@ -28,7 +28,6 @@ import java.io.IOException; import java.nio.file.Path; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Predicate; @@ -45,6 +44,7 @@ import org.lflang.lf.Element; import org.lflang.lf.KeyValuePair; import org.lflang.lf.KeyValuePairs; +import org.lflang.util.FileUtil; import org.lflang.validation.LFValidator; /** @@ -403,7 +403,7 @@ public enum TargetProperty { List.of(Target.Rust), (config, value, err) -> { Path referencePath; try { - referencePath = FileConfig.toPath(value.eResource().getURI()).toAbsolutePath(); + referencePath = FileUtil.toPath(value.eResource().getURI()).toAbsolutePath(); } catch (IOException e) { err.reportError(value, "Invalid path? " + e.getMessage()); throw new RuntimeIOException(e); diff --git a/org.lflang/src/org/lflang/generator/EclipseErrorReporter.java b/org.lflang/src/org/lflang/generator/EclipseErrorReporter.java index ab2b877a61..0429d283ba 100644 --- a/org.lflang/src/org/lflang/generator/EclipseErrorReporter.java +++ b/org.lflang/src/org/lflang/generator/EclipseErrorReporter.java @@ -38,6 +38,7 @@ import org.eclipse.xtext.validation.EObjectDiagnosticImpl; import org.lflang.ErrorReporter; import org.lflang.FileConfig; +import org.lflang.util.FileUtil; /** * An error reporter that prints messages to the command line output and also @@ -74,7 +75,7 @@ private String report(String message, Severity severity, EObject obj) { final int line = diagnostic.getLine(); Path file = null; try { - file = FileConfig.toPath(diagnostic.getUriToProblem()); + file = FileUtil.toPath(diagnostic.getUriToProblem()); } catch (IOException e) { // just continue with null } diff --git a/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java b/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java index 2c24706abc..7fe37ea289 100644 --- a/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java +++ b/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java @@ -31,14 +31,13 @@ import java.util.ArrayList; import java.util.List; -import org.eclipse.xtext.util.CancelIndicator; - import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.TargetConfig; import org.lflang.generator.GeneratorBase; import org.lflang.generator.JavaGeneratorUtils; import org.lflang.generator.LFGeneratorContext; +import org.lflang.util.FileUtil; import org.lflang.util.LFCommand; @@ -189,13 +188,13 @@ public LFCommand compileCmakeCommand( Path buildPath = fileConfig.getSrcGenPath().resolve("build"); List arguments = new ArrayList(); - arguments.addAll(List.of("-DCMAKE_INSTALL_PREFIX="+FileConfig.toUnixString(fileConfig.getOutPath()), - "-DCMAKE_INSTALL_BINDIR="+FileConfig.toUnixString( + arguments.addAll(List.of("-DCMAKE_INSTALL_PREFIX="+ FileUtil.toUnixString(fileConfig.getOutPath()), + "-DCMAKE_INSTALL_BINDIR="+ FileUtil.toUnixString( fileConfig.getOutPath().relativize( fileConfig.binPath ) ), - FileConfig.toUnixString(fileConfig.getSrcGenPath()) + FileUtil.toUnixString(fileConfig.getSrcGenPath()) )); if (JavaGeneratorUtils.isHostWindows()) { diff --git a/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java b/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java index ffa17aec2b..87e2636635 100644 --- a/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java @@ -33,6 +33,7 @@ import org.lflang.FileConfig; import org.lflang.TargetConfig; import org.lflang.generator.CodeBuilder; +import org.lflang.util.FileUtil; /** * A helper class that generates a CMakefile that can be used to compile the generated C code. @@ -82,7 +83,7 @@ CodeBuilder generateCMakeCode( for (String file: targetConfig.compileAdditionalSources) { var relativePath = fileConfig.getSrcGenPath().relativize( fileConfig.getSrcGenPath().resolve(Paths.get(file))); - additionalSources.add(FileConfig.toUnixString(relativePath)); + additionalSources.add(FileUtil.toUnixString(relativePath)); } cMakeCode.pr("cmake_minimum_required(VERSION 3.13)"); diff --git a/org.lflang/src/org/lflang/generator/c/CCompiler.java b/org.lflang/src/org/lflang/generator/c/CCompiler.java index 47efaeae8c..e6d863488d 100644 --- a/org.lflang/src/org/lflang/generator/c/CCompiler.java +++ b/org.lflang/src/org/lflang/generator/c/CCompiler.java @@ -30,14 +30,13 @@ import java.nio.file.Paths; import java.util.ArrayList; -import org.eclipse.xtext.util.CancelIndicator; - import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.TargetConfig; import org.lflang.generator.GeneratorBase; import org.lflang.generator.GeneratorCommandFactory; import org.lflang.generator.LFGeneratorContext; +import org.lflang.util.FileUtil; import org.lflang.util.LFCommand; /** @@ -169,8 +168,8 @@ public LFCommand compileCCommand( fileConfig.binPath.resolve(Paths.get(fileToCompile))); // NOTE: we assume that any C compiler takes Unix paths as arguments. - String relSrcPathString = FileConfig.toUnixString(relativeSrcPath); - String relBinPathString = FileConfig.toUnixString(relativeBinPath); + String relSrcPathString = FileUtil.toUnixString(relativeSrcPath); + String relBinPathString = FileUtil.toUnixString(relativeBinPath); // If there is no main reactor, then generate a .o file not an executable. if (noBinary) { @@ -182,7 +181,7 @@ public LFCommand compileCCommand( for (String file: targetConfig.compileAdditionalSources) { var relativePath = fileConfig.getOutPath().relativize( fileConfig.getSrcGenPath().resolve(Paths.get(file))); - compileArgs.add(FileConfig.toUnixString(relativePath)); + compileArgs.add(FileUtil.toUnixString(relativePath)); } compileArgs.addAll(targetConfig.compileLibraries); diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 050651b7b8..bae5c7cd54 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -86,6 +86,7 @@ import org.lflang.lf.StateVar import org.lflang.lf.TriggerRef import org.lflang.lf.VarRef import org.lflang.lf.Variable +import org.lflang.util.FileUtil import org.lflang.util.XtendUtil import static extension org.lflang.ASTUtils.* @@ -4351,7 +4352,7 @@ class CGenerator extends GeneratorBase { // Do this after the above includes so that the preamble can // call built-in functions. code.prComment("Code generated by the Lingua Franca compiler from:") - code.prComment("file:/" +FileConfig.toUnixString(fileConfig.srcFile)) + code.prComment("file:/" + FileUtil.toUnixString(fileConfig.srcFile)) if (this.mainDef !== null) { val mainModel = this.mainDef.reactorClass.toDefinition.eContainer as Model for (p : mainModel.preambles) { diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index e19b22642d..625368dd3c 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -49,6 +49,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.VarRef; import org.lflang.lf.Variable; import org.lflang.lf.WidthTerm; +import org.lflang.util.FileUtil; import org.lflang.util.LFCommand; /** @@ -618,7 +619,7 @@ public static void runBuildCommand( * @param fileConfig */ public static void deleteBinFiles(FileConfig fileConfig) { - String name = FileConfig.nameWithoutExtension(fileConfig.srcFile); + String name = FileUtil.nameWithoutExtension(fileConfig.srcFile); String[] files = fileConfig.binPath.toFile().list(); List federateNames = new LinkedList<>(); // FIXME: put this in ASTUtils? fileConfig.resource.getAllContents().forEachRemaining(node -> { diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index ebe303eff5..b49359dc60 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -28,6 +28,7 @@ package org.lflang.generator.ts import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.util.CancelIndicator import org.lflang.ErrorReporter +import org.lflang.FileConfig import org.lflang.InferredType import org.lflang.JavaAstUtils import org.lflang.Target @@ -201,7 +202,7 @@ class TSGenerator( */ private fun clean(context: LFGeneratorContext) { // Dirty shortcut for integrated mode: Delete nothing, saving the node_modules directory to avoid re-running pnpm. - if (context.mode != LFGeneratorContext.Mode.LSP_MEDIUM) fileConfig.deleteDirectory(fileConfig.srcGenPath) + if (context.mode != LFGeneratorContext.Mode.LSP_MEDIUM) FileConfig.deleteDirectory(fileConfig.srcGenPath) } /** diff --git a/org.lflang/src/org/lflang/util/FileUtil.java b/org.lflang/src/org/lflang/util/FileUtil.java new file mode 100644 index 0000000000..df6176ee01 --- /dev/null +++ b/org.lflang/src/org/lflang/util/FileUtil.java @@ -0,0 +1,81 @@ +package org.lflang.util; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; + +public class FileUtil { + + /** + * Return the name of the file excluding its file extension. + * @param file A Path object + * @return The name of the file excluding its file extension. + */ + public static String nameWithoutExtension(Path file) { + String name = file.getFileName().toString(); + int idx = name.lastIndexOf('.'); + return idx < 0 ? name : name.substring(0, idx); + } + + /** + * Return the name of the file associated with the given resource, + * excluding its file extension. + * @param r Any {@code Resource}. + * @return The name of the file associated with the given resource, + * excluding its file extension. + * @throws IOException If the resource has an invalid URI. + */ + public static String nameWithoutExtension(Resource r) throws IOException { + return nameWithoutExtension(toPath(r)); + } + + /** + * Return a java.nio.Path object corresponding to the given URI. + * @throws IOException If the given URI is invalid. + */ + public static Path toPath(URI uri) throws IOException { + return Paths.get(toIPath(uri).toFile().getAbsolutePath()); + } + + /** + * Return a java.nio.Path object corresponding to the given Resource. + * @throws IOException If the given resource has an invalid URI. + */ + public static Path toPath(Resource resource) throws IOException { + return toPath(resource.getURI()); + } + + /** + * Return an org.eclipse.core.runtime.Path object corresponding to the + * given URI. + * @throws IOException If the given URI is invalid. + */ + public static IPath toIPath(URI uri) throws IOException { + if (uri.isPlatform()) { + IPath path = new org.eclipse.core.runtime.Path(uri.toPlatformString(true)); + if (path.segmentCount() == 1) { + return ResourcesPlugin.getWorkspace().getRoot().getProject(path.lastSegment()).getLocation(); + } else { + return ResourcesPlugin.getWorkspace().getRoot().getFile(path).getLocation(); + } + } else if (uri.isFile()) { + return new org.eclipse.core.runtime.Path(uri.toFileString()); + } else { + throw new IOException("Unrecognized file protocol in URI " + uri); + } + } + + /** + * Convert a given path to a unix-style string. + * + * This ensures that '/' is used instead of '\' as file separator. + */ + public static String toUnixString(Path path) { + return path.toString().replace('\\', '/'); + } +} diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 8a95bf8f72..2b2850ecc3 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -56,7 +56,6 @@ import org.eclipse.xtext.validation.CheckType; import org.eclipse.xtext.validation.ValidationMessageAcceptor; import org.lflang.ASTUtils; -import org.lflang.FileConfig; import org.lflang.JavaAstUtils; import org.lflang.ModelInfo; import org.lflang.Target; @@ -102,6 +101,7 @@ import org.lflang.lf.Visibility; import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; +import org.lflang.util.FileUtil; import com.google.inject.Inject; @@ -842,7 +842,7 @@ public void checkReactor(Reactor reactor) throws IOException { // Continue checks, but without any superclasses. superClasses = new LinkedHashSet<>(); } - String name = FileConfig.nameWithoutExtension(reactor.eResource()); + String name = FileUtil.nameWithoutExtension(reactor.eResource()); if (reactor.getName() == null) { if (!reactor.isFederated() && !reactor.isMain()) { error( @@ -1040,7 +1040,7 @@ public void checkTargetDecl(TargetDecl target) throws IOException { } else { this.target = targetOpt.get(); } - String lfFileName = FileConfig.nameWithoutExtension(target.eResource()); + String lfFileName = FileUtil.nameWithoutExtension(target.eResource()); if (Character.isDigit(lfFileName.charAt(0))) { errorReporter.reportError("LF file names must not start with a number"); } From b972a6d529d8fb6f24b22f49a3e47e6b9fa6e9fd Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 16:10:27 +0100 Subject: [PATCH 14/30] make various copy methods static --- org.lflang/src/org/lflang/FileConfig.java | 16 ++++++++-------- .../src/org/lflang/generator/cpp/CppGenerator.kt | 9 +++++---- .../src/org/lflang/generator/ts/TSGenerator.kt | 4 ++-- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/org.lflang/src/org/lflang/FileConfig.java b/org.lflang/src/org/lflang/FileConfig.java index 36bb59e196..4610c1544b 100644 --- a/org.lflang/src/org/lflang/FileConfig.java +++ b/org.lflang/src/org/lflang/FileConfig.java @@ -432,8 +432,8 @@ private static void copyInputStream(InputStream source, Path destination, boolea * @param skipIfUnchanged If true, don't overwrite the destination file if its content would not be changed * @throws IOException If the given source cannot be copied. */ - public void copyFileFromClassPath(final String source, final Path destination, final boolean skipIfUnchanged) throws IOException { - InputStream sourceStream = this.getClass().getResourceAsStream(source); + public static void copyFileFromClassPath(final String source, final Path destination, final boolean skipIfUnchanged) throws IOException { + InputStream sourceStream = FileConfig.class.getResourceAsStream(source); // Copy the file. if (sourceStream == null) { @@ -461,7 +461,7 @@ public void copyFileFromClassPath(final String source, final Path destination, f * @param destination The file system path that the source file is copied to. * @throws IOException If the given source cannot be copied. */ - public void copyFileFromClassPath(final String source, final Path destination) throws IOException { + public static void copyFileFromClassPath(final String source, final Path destination) throws IOException { copyFileFromClassPath(source, destination, false); } @@ -477,8 +477,8 @@ public void copyFileFromClassPath(final String source, final Path destination) t * @param skipIfUnchanged If true, don't overwrite the file if its content would not be changed * @throws IOException If the given source cannot be copied. */ - public void copyDirectoryFromClassPath(final String source, final Path destination, final boolean skipIfUnchanged) throws IOException { - final URL resource = getClass().getResource(source); + public static void copyDirectoryFromClassPath(final String source, final Path destination, final boolean skipIfUnchanged) throws IOException { + final URL resource = FileConfig.class.getResource(source); if (resource == null) { throw new IOException( "A required target resource could not be found: " + source + "\n" + @@ -515,7 +515,7 @@ public void copyDirectoryFromClassPath(final String source, final Path destinati * @param skipIfUnchanged If true, don't overwrite the file if its content would not be changed * @throws IOException If the given source cannot be copied. */ - private void copyDirectoryFromJar(JarURLConnection connection, final Path destination, final boolean skipIfUnchanged) throws IOException { + private static void copyDirectoryFromJar(JarURLConnection connection, final Path destination, final boolean skipIfUnchanged) throws IOException { final JarFile jar = connection.getJarFile(); final String connectionEntryName = connection.getEntryName(); @@ -548,7 +548,7 @@ private void copyDirectoryFromJar(JarURLConnection connection, final Path destin * @param files The list of files to copy. * @throws IOException If any of the given files cannot be copied. */ - public void copyFilesFromClassPath(String srcDir, Path dstDir, List files) throws IOException { + public static void copyFilesFromClassPath(String srcDir, Path dstDir, List files) throws IOException { for (String file : files) { copyFileFromClassPath(srcDir + '/' + file, dstDir.resolve(file)); } @@ -568,7 +568,7 @@ public void copyFilesFromClassPath(String srcDir, Path dstDir, List file * @param dstDir Where the file should be placed * @return The name of the file in destinationDirectory */ - public String copyFileOrResource(String fileName, Path srcDir, Path dstDir) { + public static String copyFileOrResource(String fileName, Path srcDir, Path dstDir) { // Try to copy the file from the file system. Path file = findFile(fileName, srcDir); if (file != null) { diff --git a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt index 2ae7fc107a..21a07da539 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt @@ -28,6 +28,7 @@ package org.lflang.generator.cpp import org.eclipse.emf.ecore.resource.Resource import org.lflang.ErrorReporter +import org.lflang.FileConfig import org.lflang.Target import org.lflang.generator.LFGeneratorContext.Mode import org.lflang.TargetProperty @@ -127,9 +128,9 @@ class CppGenerator( // copy static library files over to the src-gen directory val genIncludeDir = srcGenPath.resolve("__include__") - fileConfig.copyFileFromClassPath("$libDir/lfutil.hh", genIncludeDir.resolve("lfutil.hh"), true) - fileConfig.copyFileFromClassPath("$libDir/time_parser.hh", genIncludeDir.resolve("time_parser.hh"), true) - fileConfig.copyFileFromClassPath( + FileConfig.copyFileFromClassPath("$libDir/lfutil.hh", genIncludeDir.resolve("lfutil.hh"), true) + FileConfig.copyFileFromClassPath("$libDir/time_parser.hh", genIncludeDir.resolve("time_parser.hh"), true) + FileConfig.copyFileFromClassPath( "$libDir/3rd-party/cxxopts.hpp", genIncludeDir.resolve("CLI").resolve("cxxopts.hpp"), true @@ -140,7 +141,7 @@ class CppGenerator( if (targetConfig.runtimeVersion != null) { fetchReactorCpp() } else { - fileConfig.copyDirectoryFromClassPath( + FileConfig.copyDirectoryFromClassPath( "$libDir/reactor-cpp", fileConfig.srcGenBasePath.resolve("reactor-cpp-lfbuiltin"), true diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index b49359dc60..3bafbf77d0 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -210,7 +210,7 @@ class TSGenerator( */ private fun copyRuntime() { for (runtimeFile in RUNTIME_FILES) { - fileConfig.copyFileFromClassPath( + FileConfig.copyFileFromClassPath( "$LIB_PATH/reactor-ts/src/core/$runtimeFile", tsFileConfig.tsCoreGenPath().resolve(runtimeFile) ) @@ -233,7 +233,7 @@ class TSGenerator( "No '" + configFile + "' exists in " + fileConfig.srcPath + ". Using default configuration." ) - fileConfig.copyFileFromClassPath("$LIB_PATH/$configFile", configFileDest) + FileConfig.copyFileFromClassPath("$LIB_PATH/$configFile", configFileDest) } } } From 8b001824f516ea47909eea54da0bf93cb5fc5fb1 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 16:22:56 +0100 Subject: [PATCH 15/30] move all copy methods to FileUtil --- org.lflang/src/org/lflang/FileConfig.java | 319 +----------------- .../org/lflang/generator/GeneratorBase.xtend | 3 +- .../lflang/generator/c/CCmakeCompiler.java | 2 +- .../org/lflang/generator/c/CGenerator.xtend | 8 +- .../org/lflang/generator/cpp/CppFileConfig.kt | 5 +- .../org/lflang/generator/cpp/CppGenerator.kt | 18 +- .../generator/python/PythonGenerator.xtend | 7 +- .../org/lflang/generator/rust/RustEmitter.kt | 6 +- .../lflang/generator/rust/RustFileConfig.kt | 4 +- .../org/lflang/generator/ts/TSFileConfig.kt | 4 +- .../org/lflang/generator/ts/TSGenerator.kt | 10 +- org.lflang/src/org/lflang/util/FileUtil.java | 319 ++++++++++++++++++ 12 files changed, 360 insertions(+), 345 deletions(-) diff --git a/org.lflang/src/org/lflang/FileConfig.java b/org.lflang/src/org/lflang/FileConfig.java index 4610c1544b..93a160d0f0 100644 --- a/org.lflang/src/org/lflang/FileConfig.java +++ b/org.lflang/src/org/lflang/FileConfig.java @@ -1,38 +1,20 @@ package org.lflang; -import java.io.BufferedInputStream; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; -import java.net.JarURLConnection; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Enumeration; -import java.util.List; import java.util.function.Consumer; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.FileLocator; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.IFileSystemAccess2; -import org.eclipse.xtext.util.RuntimeIOException; import org.lflang.util.FileUtil; @@ -320,303 +302,6 @@ protected static Path getSubPkgPath(Path pkgPath, Path srcPath) { return relSrcPath; } - /** - * Recursively copies the contents of the given 'src' - * directory to 'dest'. Existing files of the destination - * may be overwritten. - * - * @param src The source directory path. - * @param dest The destination directory path. - * @param skipIfUnchanged If true, don't overwrite the destination file if its content would not be changed - * @throws IOException if copy fails. - */ - public static void copyDirectory(final Path src, final Path dest, final boolean skipIfUnchanged) throws IOException { - try (Stream stream = Files.walk(src)) { - stream.forEach(source -> { - // Handling checked exceptions in lambda expressions is - // hard. See - // https://www.baeldung.com/java-lambda-exceptions#handling-checked-exceptions. - // An alternative would be to create a custom Consumer interface and use that - // here. - if (Files.isRegularFile(source)) { // do not copy directories - try { - Path target = dest.resolve(src.relativize(source)); - Files.createDirectories(target.getParent()); - copyFile(source, target, skipIfUnchanged); - } catch (IOException e) { - throw new RuntimeIOException(e); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - }); - } - } - - /** - * Recursively copies the contents of the given 'src' - * directory to 'dest'. Existing files of the destination - * may be overwritten. - * - * @param src The source directory path. - * @param dest The destination directory path. - * @throws IOException if copy fails. - */ - public static void copyDirectory(final Path src, final Path dest) throws IOException { - copyDirectory(src, dest, false); - } - - /** - * Copy a given file from 'source' to 'destination'. - * - * This also creates new directories for any directories on the destination - * path that do not yet exist. - * - * @param source The source file path. - * @param destination The destination file path. - * @param skipIfUnchanged If true, don't overwrite the destination file if its content would not be changed - * @throws IOException if copy fails. - */ - public static void copyFile(Path source, Path destination, boolean skipIfUnchanged) throws IOException { - BufferedInputStream stream = new BufferedInputStream(new FileInputStream(source.toFile())); - try (stream) { - copyInputStream(stream, destination, skipIfUnchanged); - } - } - - /** - * Copy a given file from 'source' to 'destination'. - * - * This also creates new directories for any directories on the destination - * path that do not yet exist. - * - * @param source The source file path. - * @param destination The destination file path. - * @throws IOException if copy fails. - */ - public static void copyFile(Path source, Path destination) throws IOException { - copyFile(source, destination, false); - } - - /** - * Copy a given input stream to a destination file. - * - * This also creates new directories for any directories on the destination - * path that do not yet exist. - * - * @param source The source input stream. - * @param destination The destination file path. - * @param skipIfUnchanged If true, don't overwrite the destination file if its content would not be changed - * @throws IOException if copy fails. - */ - private static void copyInputStream(InputStream source, Path destination, boolean skipIfUnchanged) throws IOException { - Files.createDirectories(destination.getParent()); - if(skipIfUnchanged && Files.isRegularFile(destination)) { - if (Arrays.equals(source.readAllBytes(), Files.readAllBytes(destination))) { - return; - } - } - Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING); - } - - - /** - * Lookup a file in the classpath and copy its contents to a destination path - * in the filesystem. - * - * This also creates new directories for any directories on the destination - * path that do not yet exist. - * - * @param source The source file as a path relative to the classpath. - * @param destination The file system path that the source file is copied to. - * @param skipIfUnchanged If true, don't overwrite the destination file if its content would not be changed - * @throws IOException If the given source cannot be copied. - */ - public static void copyFileFromClassPath(final String source, final Path destination, final boolean skipIfUnchanged) throws IOException { - InputStream sourceStream = FileConfig.class.getResourceAsStream(source); - - // Copy the file. - if (sourceStream == null) { - throw new IOException( - "A required target resource could not be found: " + source + "\n" + - "Perhaps a git submodule is missing or not up to date.\n" + - "See https://github.com/icyphy/lingua-franca/wiki/downloading-and-building#clone-the-lingua-franca-repository.\n" - + - "Also try to refresh and clean the project explorer if working from eclipse."); - } else { - try (sourceStream) { - copyInputStream(sourceStream, destination, skipIfUnchanged); - } - } - } - - /** - * Lookup a file in the classpath and copy its contents to a destination path - * in the filesystem. - * - * This also creates new directories for any directories on the destination - * path that do not yet exist. - * - * @param source The source file as a path relative to the classpath. - * @param destination The file system path that the source file is copied to. - * @throws IOException If the given source cannot be copied. - */ - public static void copyFileFromClassPath(final String source, final Path destination) throws IOException { - copyFileFromClassPath(source, destination, false); - } - - /** - * Lookup a directory in the classpath and copy its contents to a destination path - * in the filesystem. - * - * This also creates new directories for any directories on the destination - * path that do not yet exist. - * - * @param source The source directory as a path relative to the classpath. - * @param destination The file system path that the source directory is copied to. - * @param skipIfUnchanged If true, don't overwrite the file if its content would not be changed - * @throws IOException If the given source cannot be copied. - */ - public static void copyDirectoryFromClassPath(final String source, final Path destination, final boolean skipIfUnchanged) throws IOException { - final URL resource = FileConfig.class.getResource(source); - if (resource == null) { - throw new IOException( - "A required target resource could not be found: " + source + "\n" + - "Perhaps a git submodule is missing or not up to date.\n" + - "See https://github.com/icyphy/lingua-franca/wiki/downloading-and-building#clone-the-lingua-franca-repository.\n" - + - "Also try to refresh and clean the project explorer if working from eclipse."); - } - - final URLConnection connection = resource.openConnection(); - if (connection instanceof JarURLConnection) { - copyDirectoryFromJar((JarURLConnection) connection, destination, skipIfUnchanged); - } else { - try { - Path dir = Paths.get(FileLocator.toFileURL(resource).toURI()); - copyDirectory(dir, destination, skipIfUnchanged); - } catch(URISyntaxException e) { - // This should never happen as toFileURL should always return a valid URL - throw new IOException("Unexpected error while resolving " + source + " on the classpath"); - } - } - } - - /** - * Copy a directory from ta jar to a destination path in the filesystem. - * - * This method should only be used in standalone mode (lfc). - * - * This also creates new directories for any directories on the destination - * path that do not yet exist - * - * @param connection a URLConnection to the source directory within the jar - * @param destination The file system path that the source directory is copied to. - * @param skipIfUnchanged If true, don't overwrite the file if its content would not be changed - * @throws IOException If the given source cannot be copied. - */ - private static void copyDirectoryFromJar(JarURLConnection connection, final Path destination, final boolean skipIfUnchanged) throws IOException { - final JarFile jar = connection.getJarFile(); - final String connectionEntryName = connection.getEntryName(); - - // Iterate all entries in the jar file. - for (Enumeration e = jar.entries(); e.hasMoreElements(); ) { - final JarEntry entry = e.nextElement(); - final String entryName = entry.getName(); - - // Extract files only if they match the given source path. - if (entryName.startsWith(connectionEntryName)) { - String filename = entry.getName().substring(connectionEntryName.length() + 1); - Path currentFile = destination.resolve(filename); - - if (entry.isDirectory()) { - Files.createDirectories(currentFile); - } else { - InputStream is = jar.getInputStream(entry); - try (is) { - copyInputStream(is, currentFile, skipIfUnchanged); - } - } - } - } - } - - /** - * Copy a list of files from a given source directory to a given destination directory. - * @param srcDir The directory to copy files from. - * @param dstDir The directory to copy files to. - * @param files The list of files to copy. - * @throws IOException If any of the given files cannot be copied. - */ - public static void copyFilesFromClassPath(String srcDir, Path dstDir, List files) throws IOException { - for (String file : files) { - copyFileFromClassPath(srcDir + '/' + file, dstDir.resolve(file)); - } - } - - /** - * Copy the 'fileName' from the 'srcDirectory' to the 'destinationDirectory'. - * This function has a fallback search mechanism, where if `fileName` is not - * found in the `srcDirectory`, it will try to find `fileName` via the following procedure: - * 1- Search in LF_CLASSPATH. @see findFile() - * 2- Search in CLASSPATH. @see findFile() - * 3- Search for 'fileName' as a resource. - * That means the `fileName` can be '/path/to/class/resource'. @see java.lang.Class.getResourceAsStream() - * - * @param fileName Name of the file - * @param srcDir Where the file is currently located - * @param dstDir Where the file should be placed - * @return The name of the file in destinationDirectory - */ - public static String copyFileOrResource(String fileName, Path srcDir, Path dstDir) { - // Try to copy the file from the file system. - Path file = findFile(fileName, srcDir); - if (file != null) { - Path target = dstDir.resolve(file.getFileName()); - try { - Files.copy(file, target, StandardCopyOption.REPLACE_EXISTING); - return file.getFileName().toString(); - } catch (IOException e) { - // Files has failed to copy the file, possibly since - // it doesn't exist. Will try to find the file as a - // resource before giving up. - } - } - - // Try to copy the file as a resource. - // If this is missing, it should have been previously reported as an error. - try { - String filenameWithoutPath = fileName; - int lastSeparator = fileName.lastIndexOf(File.separator); - if (lastSeparator > 0) { - filenameWithoutPath = fileName.substring(lastSeparator + 1); // FIXME: brittle. What if the file is in a subdirectory? - } - copyFileFromClassPath(fileName, dstDir.resolve(filenameWithoutPath)); - return filenameWithoutPath; - } catch (IOException ex) { - // Ignore. Previously reported as a warning. - System.err.println("WARNING: Failed to find file " + fileName); - } - - return ""; - } - - /** - * Recursively delete a directory if it exists. - * - * @throws IOException If an I/O error occurs. - */ - public static void deleteDirectory(Path dir) throws IOException { - if (Files.isDirectory(dir)) { - System.out.println("Cleaning " + dir); - List pathsToDelete = Files.walk(dir) - .sorted(Comparator.reverseOrder()) - .collect(Collectors.toList()); - for (Path path : pathsToDelete) { - Files.deleteIfExists(path); - } - } - } /** * Clean any artifacts produced by the code generator and target compilers. @@ -628,8 +313,8 @@ public static void deleteDirectory(Path dir) throws IOException { * @throws IOException If an I/O error occurs. */ public void doClean() throws IOException { - deleteDirectory(binPath); - deleteDirectory(srcGenBasePath); + FileUtil.deleteDirectory(binPath); + FileUtil.deleteDirectory(srcGenBasePath); } private static Path getPkgPath(Resource resource) throws IOException { diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 5bb99ea475..b736a6ce19 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -65,6 +65,7 @@ import org.lflang.lf.Reactor import org.lflang.lf.Time import org.lflang.lf.Value import org.lflang.lf.VarRef +import org.lflang.util.FileUtil import static extension org.lflang.ASTUtils.* import org.lflang.validation.AbstractLFValidator @@ -416,7 +417,7 @@ abstract class GeneratorBase extends AbstractLFValidator { Files.createDirectories(targetDir) for (filename : targetConfig.fileNames) { - val relativeFileName = fileConfig.copyFileOrResource( + val relativeFileName = FileUtil.copyFileOrResource( filename, fileConfig.srcFile.parent, targetDir); diff --git a/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java b/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java index 7fe37ea289..4aeef374e7 100644 --- a/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java +++ b/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java @@ -104,7 +104,7 @@ public boolean runCCompiler( // has previously occurred. Deleting the build directory // if no prior errors have occurred can prolong the compilation // substantially. - fileConfig.deleteDirectory(buildPath); + FileUtil.deleteDirectory(buildPath); // Make sure the build directory exists Files.createDirectories(buildPath); diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index bae5c7cd54..7b65b6fb45 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -569,7 +569,7 @@ class CGenerator extends GeneratorBase { } // Copy the core lib - fileConfig.copyFilesFromClassPath("/lib/c/reactor-c/core", fileConfig.getSrcGenPath.resolve("core"), coreFiles) + FileUtil.copyFilesFromClassPath("/lib/c/reactor-c/core", fileConfig.getSrcGenPath.resolve("core"), coreFiles) // Copy the header files copyTargetHeaderFile() @@ -1057,7 +1057,7 @@ class CGenerator extends GeneratorBase { val targetDir = this.fileConfig.getSrcGenPath for (filename : targetConfig.cmakeIncludes) { val relativeCMakeIncludeFileName = - fileConfig.copyFileOrResource( + FileUtil.copyFileOrResource( filename, fileConfig.srcFile.parent, targetDir); @@ -1511,8 +1511,8 @@ class CGenerator extends GeneratorBase { * Copy target-specific header file to the src-gen directory. */ def copyTargetHeaderFile() { - fileConfig.copyFileFromClassPath("/lib/c/reactor-c/include/ctarget.h", fileConfig.getSrcGenPath.resolve("ctarget.h")) - fileConfig.copyFileFromClassPath("/lib/c/reactor-c/lib/ctarget.c", fileConfig.getSrcGenPath.resolve("ctarget.c")) + FileUtil.copyFileFromClassPath("/lib/c/reactor-c/include/ctarget.h", fileConfig.getSrcGenPath.resolve("ctarget.h")) + FileUtil.copyFileFromClassPath("/lib/c/reactor-c/lib/ctarget.c", fileConfig.getSrcGenPath.resolve("ctarget.c")) } //////////////////////////////////////////// diff --git a/org.lflang/src/org/lflang/generator/cpp/CppFileConfig.kt b/org.lflang/src/org/lflang/generator/cpp/CppFileConfig.kt index 0300da0d00..8ca1b41fd0 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppFileConfig.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppFileConfig.kt @@ -26,11 +26,10 @@ package org.lflang.generator.cpp import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.xtext.generator.IFileSystemAccess2 import org.lflang.FileConfig -import org.lflang.generator.LFGeneratorContext import org.lflang.lf.Reactor import org.lflang.name +import org.lflang.util.FileUtil import java.io.IOException import java.nio.file.Path @@ -43,7 +42,7 @@ class CppFileConfig(resource: Resource, srcGenBasePath: Path, useHierarchicalBin @Throws(IOException::class) override fun doClean() { super.doClean() - cppBuildDirectories.forEach { deleteDirectory(it) } + cppBuildDirectories.forEach { FileUtil.deleteDirectory(it) } } val cppBuildDirectories = listOf( diff --git a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt index 21a07da539..611a473aaf 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt @@ -28,7 +28,6 @@ package org.lflang.generator.cpp import org.eclipse.emf.ecore.resource.Resource import org.lflang.ErrorReporter -import org.lflang.FileConfig import org.lflang.Target import org.lflang.generator.LFGeneratorContext.Mode import org.lflang.TargetProperty @@ -48,6 +47,7 @@ import org.lflang.lf.VarRef import org.lflang.scoping.LFGlobalScopeProvider import org.lflang.toDefinition import org.lflang.toUnixString +import org.lflang.util.FileUtil import org.lflang.util.LFCommand import java.nio.file.Files import java.nio.file.Path @@ -128,9 +128,17 @@ class CppGenerator( // copy static library files over to the src-gen directory val genIncludeDir = srcGenPath.resolve("__include__") - FileConfig.copyFileFromClassPath("$libDir/lfutil.hh", genIncludeDir.resolve("lfutil.hh"), true) - FileConfig.copyFileFromClassPath("$libDir/time_parser.hh", genIncludeDir.resolve("time_parser.hh"), true) - FileConfig.copyFileFromClassPath( + FileUtil.copyFileFromClassPath( + "$libDir/lfutil.hh", + genIncludeDir.resolve("lfutil.hh"), + true + ) + FileUtil.copyFileFromClassPath( + "$libDir/time_parser.hh", + genIncludeDir.resolve("time_parser.hh"), + true + ) + FileUtil.copyFileFromClassPath( "$libDir/3rd-party/cxxopts.hpp", genIncludeDir.resolve("CLI").resolve("cxxopts.hpp"), true @@ -141,7 +149,7 @@ class CppGenerator( if (targetConfig.runtimeVersion != null) { fetchReactorCpp() } else { - FileConfig.copyDirectoryFromClassPath( + FileUtil.copyDirectoryFromClassPath( "$libDir/reactor-cpp", fileConfig.srcGenBasePath.resolve("reactor-cpp-lfbuiltin"), true diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 61e9330214..b1f74ff2b1 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -76,6 +76,7 @@ import org.lflang.lf.StateVar import org.lflang.lf.TriggerRef import org.lflang.lf.Value import org.lflang.lf.VarRef +import org.lflang.util.FileUtil import static extension org.lflang.ASTUtils.* import static extension org.lflang.JavaAstUtils.* @@ -1382,15 +1383,15 @@ class PythonGenerator extends CGenerator { def copyTargetFiles() { // Copy the required target language files into the target file system. // This will also overwrite previous versions. - fileConfig.copyFileFromClassPath( + FileUtil.copyFileFromClassPath( "/lib/py/reactor-c-py/include/pythontarget.h", fileConfig.getSrcGenPath.resolve("pythontarget.h") ) - fileConfig.copyFileFromClassPath( + FileUtil.copyFileFromClassPath( "/lib/py/reactor-c-py/lib/pythontarget.c", fileConfig.getSrcGenPath.resolve("pythontarget.c") ) - fileConfig.copyFileFromClassPath( + FileUtil.copyFileFromClassPath( "/lib/c/reactor-c/include/ctarget.h", fileConfig.getSrcGenPath.resolve("ctarget.h") ) diff --git a/org.lflang/src/org/lflang/generator/rust/RustEmitter.kt b/org.lflang/src/org/lflang/generator/rust/RustEmitter.kt index b9339986f4..6e14238127 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustEmitter.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustEmitter.kt @@ -24,10 +24,10 @@ package org.lflang.generator.rust -import org.lflang.FileConfig import org.lflang.generator.CodeMap import org.lflang.generator.PrependOperator import org.lflang.generator.rust.RustEmitter.generateRustProject +import org.lflang.util.FileUtil import java.nio.file.Files import java.nio.file.Path @@ -77,9 +77,9 @@ object RustEmitter : RustEmitterBase() { for (modPath in gen.crate.modulesToIncludeInMain) { val target = fileConfig.srcGenPath.resolve("src").resolve(modPath.fileName) if (Files.isDirectory(modPath)) { - FileConfig.copyDirectory(modPath, target) + FileUtil.copyDirectory(modPath, target) } else { - FileConfig.copyFile(modPath, target) + FileUtil.copyFile(modPath, target) } } return codeMaps diff --git a/org.lflang/src/org/lflang/generator/rust/RustFileConfig.kt b/org.lflang/src/org/lflang/generator/rust/RustFileConfig.kt index 4545dbd4b1..95e287d005 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustFileConfig.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustFileConfig.kt @@ -27,7 +27,7 @@ package org.lflang.generator.rust import org.eclipse.emf.ecore.resource.Resource import org.lflang.FileConfig import org.lflang.generator.CodeMap -import org.lflang.generator.LFGeneratorContext +import org.lflang.util.FileUtil import java.io.Closeable import java.io.IOException import java.nio.file.Files @@ -43,7 +43,7 @@ class RustFileConfig(resource: Resource, srcGenBasePath: Path, useHierarchicalBi @Throws(IOException::class) override fun doClean() { super.doClean() - deleteDirectory(outPath.resolve("target")) + FileUtil.deleteDirectory(outPath.resolve("target")) } inline fun emit(codeMaps: MutableMap, p: Path, f: Emitter.() -> Unit) { diff --git a/org.lflang/src/org/lflang/generator/ts/TSFileConfig.kt b/org.lflang/src/org/lflang/generator/ts/TSFileConfig.kt index 6ff1940916..fd844bf912 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSFileConfig.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSFileConfig.kt @@ -27,7 +27,7 @@ package org.lflang.generator.ts import org.eclipse.emf.ecore.resource.Resource import org.lflang.FileConfig -import org.lflang.generator.LFGeneratorContext +import org.lflang.util.FileUtil import java.io.IOException import java.nio.file.Path @@ -50,7 +50,7 @@ class TSFileConfig( @Throws(IOException::class) override fun doClean() { super.doClean() - deleteDirectory(srcGenPath) + FileUtil.deleteDirectory(srcGenPath) } /** diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index 3bafbf77d0..165e9e5a35 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -28,7 +28,6 @@ package org.lflang.generator.ts import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.util.CancelIndicator import org.lflang.ErrorReporter -import org.lflang.FileConfig import org.lflang.InferredType import org.lflang.JavaAstUtils import org.lflang.Target @@ -57,6 +56,7 @@ import org.lflang.lf.Type import org.lflang.lf.Value import org.lflang.lf.VarRef import org.lflang.scoping.LFGlobalScopeProvider +import org.lflang.util.FileUtil import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardCopyOption @@ -202,7 +202,9 @@ class TSGenerator( */ private fun clean(context: LFGeneratorContext) { // Dirty shortcut for integrated mode: Delete nothing, saving the node_modules directory to avoid re-running pnpm. - if (context.mode != LFGeneratorContext.Mode.LSP_MEDIUM) FileConfig.deleteDirectory(fileConfig.srcGenPath) + if (context.mode != LFGeneratorContext.Mode.LSP_MEDIUM) FileUtil.deleteDirectory( + fileConfig.srcGenPath + ) } /** @@ -210,7 +212,7 @@ class TSGenerator( */ private fun copyRuntime() { for (runtimeFile in RUNTIME_FILES) { - FileConfig.copyFileFromClassPath( + FileUtil.copyFileFromClassPath( "$LIB_PATH/reactor-ts/src/core/$runtimeFile", tsFileConfig.tsCoreGenPath().resolve(runtimeFile) ) @@ -233,7 +235,7 @@ class TSGenerator( "No '" + configFile + "' exists in " + fileConfig.srcPath + ". Using default configuration." ) - FileConfig.copyFileFromClassPath("$LIB_PATH/$configFile", configFileDest) + FileUtil.copyFileFromClassPath("$LIB_PATH/$configFile", configFileDest) } } } diff --git a/org.lflang/src/org/lflang/util/FileUtil.java b/org.lflang/src/org/lflang/util/FileUtil.java index df6176ee01..10a74c3808 100644 --- a/org.lflang/src/org/lflang/util/FileUtil.java +++ b/org.lflang/src/org/lflang/util/FileUtil.java @@ -1,13 +1,35 @@ package org.lflang.util; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; +import java.net.JarURLConnection; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IPath; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.util.RuntimeIOException; + +import org.lflang.FileConfig; public class FileUtil { @@ -78,4 +100,301 @@ public static IPath toIPath(URI uri) throws IOException { public static String toUnixString(Path path) { return path.toString().replace('\\', '/'); } + + /** + * Recursively copies the contents of the given 'src' + * directory to 'dest'. Existing files of the destination + * may be overwritten. + * + * @param src The source directory path. + * @param dest The destination directory path. + * @param skipIfUnchanged If true, don't overwrite the destination file if its content would not be changed + * @throws IOException if copy fails. + */ + public static void copyDirectory(final Path src, final Path dest, final boolean skipIfUnchanged) throws IOException { + try (Stream stream = Files.walk(src)) { + stream.forEach(source -> { + // Handling checked exceptions in lambda expressions is + // hard. See + // https://www.baeldung.com/java-lambda-exceptions#handling-checked-exceptions. + // An alternative would be to create a custom Consumer interface and use that + // here. + if (Files.isRegularFile(source)) { // do not copy directories + try { + Path target = dest.resolve(src.relativize(source)); + Files.createDirectories(target.getParent()); + copyFile(source, target, skipIfUnchanged); + } catch (IOException e) { + throw new RuntimeIOException(e); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + } + } + + /** + * Recursively copies the contents of the given 'src' + * directory to 'dest'. Existing files of the destination + * may be overwritten. + * + * @param src The source directory path. + * @param dest The destination directory path. + * @throws IOException if copy fails. + */ + public static void copyDirectory(final Path src, final Path dest) throws IOException { + copyDirectory(src, dest, false); + } + + /** + * Copy a given file from 'source' to 'destination'. + * + * This also creates new directories for any directories on the destination + * path that do not yet exist. + * + * @param source The source file path. + * @param destination The destination file path. + * @param skipIfUnchanged If true, don't overwrite the destination file if its content would not be changed + * @throws IOException if copy fails. + */ + public static void copyFile(Path source, Path destination, boolean skipIfUnchanged) throws IOException { + BufferedInputStream stream = new BufferedInputStream(new FileInputStream(source.toFile())); + try (stream) { + copyInputStream(stream, destination, skipIfUnchanged); + } + } + + /** + * Copy a given file from 'source' to 'destination'. + * + * This also creates new directories for any directories on the destination + * path that do not yet exist. + * + * @param source The source file path. + * @param destination The destination file path. + * @throws IOException if copy fails. + */ + public static void copyFile(Path source, Path destination) throws IOException { + copyFile(source, destination, false); + } + + /** + * Copy a given input stream to a destination file. + * + * This also creates new directories for any directories on the destination + * path that do not yet exist. + * + * @param source The source input stream. + * @param destination The destination file path. + * @param skipIfUnchanged If true, don't overwrite the destination file if its content would not be changed + * @throws IOException if copy fails. + */ + private static void copyInputStream(InputStream source, Path destination, boolean skipIfUnchanged) throws IOException { + Files.createDirectories(destination.getParent()); + if(skipIfUnchanged && Files.isRegularFile(destination)) { + if (Arrays.equals(source.readAllBytes(), Files.readAllBytes(destination))) { + return; + } + } + Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING); + } + + /** + * Lookup a file in the classpath and copy its contents to a destination path + * in the filesystem. + * + * This also creates new directories for any directories on the destination + * path that do not yet exist. + * + * @param source The source file as a path relative to the classpath. + * @param destination The file system path that the source file is copied to. + * @param skipIfUnchanged If true, don't overwrite the destination file if its content would not be changed + * @throws IOException If the given source cannot be copied. + */ + public static void copyFileFromClassPath(final String source, final Path destination, final boolean skipIfUnchanged) throws IOException { + InputStream sourceStream = FileConfig.class.getResourceAsStream(source); + + // Copy the file. + if (sourceStream == null) { + throw new IOException( + "A required target resource could not be found: " + source + "\n" + + "Perhaps a git submodule is missing or not up to date.\n" + + "See https://github.com/icyphy/lingua-franca/wiki/downloading-and-building#clone-the-lingua-franca-repository.\n" + + + "Also try to refresh and clean the project explorer if working from eclipse."); + } else { + try (sourceStream) { + copyInputStream(sourceStream, destination, skipIfUnchanged); + } + } + } + + /** + * Lookup a file in the classpath and copy its contents to a destination path + * in the filesystem. + * + * This also creates new directories for any directories on the destination + * path that do not yet exist. + * + * @param source The source file as a path relative to the classpath. + * @param destination The file system path that the source file is copied to. + * @throws IOException If the given source cannot be copied. + */ + public static void copyFileFromClassPath(final String source, final Path destination) throws IOException { + copyFileFromClassPath(source, destination, false); + } + + /** + * Lookup a directory in the classpath and copy its contents to a destination path + * in the filesystem. + * + * This also creates new directories for any directories on the destination + * path that do not yet exist. + * + * @param source The source directory as a path relative to the classpath. + * @param destination The file system path that the source directory is copied to. + * @param skipIfUnchanged If true, don't overwrite the file if its content would not be changed + * @throws IOException If the given source cannot be copied. + */ + public static void copyDirectoryFromClassPath(final String source, final Path destination, final boolean skipIfUnchanged) throws IOException { + final URL resource = FileConfig.class.getResource(source); + if (resource == null) { + throw new IOException( + "A required target resource could not be found: " + source + "\n" + + "Perhaps a git submodule is missing or not up to date.\n" + + "See https://github.com/icyphy/lingua-franca/wiki/downloading-and-building#clone-the-lingua-franca-repository.\n" + + + "Also try to refresh and clean the project explorer if working from eclipse."); + } + + final URLConnection connection = resource.openConnection(); + if (connection instanceof JarURLConnection) { + copyDirectoryFromJar((JarURLConnection) connection, destination, skipIfUnchanged); + } else { + try { + Path dir = Paths.get(FileLocator.toFileURL(resource).toURI()); + copyDirectory(dir, destination, skipIfUnchanged); + } catch(URISyntaxException e) { + // This should never happen as toFileURL should always return a valid URL + throw new IOException("Unexpected error while resolving " + source + " on the classpath"); + } + } + } + + /** + * Copy a directory from ta jar to a destination path in the filesystem. + * + * This method should only be used in standalone mode (lfc). + * + * This also creates new directories for any directories on the destination + * path that do not yet exist + * + * @param connection a URLConnection to the source directory within the jar + * @param destination The file system path that the source directory is copied to. + * @param skipIfUnchanged If true, don't overwrite the file if its content would not be changed + * @throws IOException If the given source cannot be copied. + */ + private static void copyDirectoryFromJar(JarURLConnection connection, final Path destination, final boolean skipIfUnchanged) throws IOException { + final JarFile jar = connection.getJarFile(); + final String connectionEntryName = connection.getEntryName(); + + // Iterate all entries in the jar file. + for (Enumeration e = jar.entries(); e.hasMoreElements(); ) { + final JarEntry entry = e.nextElement(); + final String entryName = entry.getName(); + + // Extract files only if they match the given source path. + if (entryName.startsWith(connectionEntryName)) { + String filename = entry.getName().substring(connectionEntryName.length() + 1); + Path currentFile = destination.resolve(filename); + + if (entry.isDirectory()) { + Files.createDirectories(currentFile); + } else { + InputStream is = jar.getInputStream(entry); + try (is) { + copyInputStream(is, currentFile, skipIfUnchanged); + } + } + } + } + } + + /** + * Copy a list of files from a given source directory to a given destination directory. + * @param srcDir The directory to copy files from. + * @param dstDir The directory to copy files to. + * @param files The list of files to copy. + * @throws IOException If any of the given files cannot be copied. + */ + public static void copyFilesFromClassPath(String srcDir, Path dstDir, List files) throws IOException { + for (String file : files) { + copyFileFromClassPath(srcDir + '/' + file, dstDir.resolve(file)); + } + } + + /** + * Copy the 'fileName' from the 'srcDirectory' to the 'destinationDirectory'. + * This function has a fallback search mechanism, where if `fileName` is not + * found in the `srcDirectory`, it will try to find `fileName` via the following procedure: + * 1- Search in LF_CLASSPATH. @see findFile() + * 2- Search in CLASSPATH. @see findFile() + * 3- Search for 'fileName' as a resource. + * That means the `fileName` can be '/path/to/class/resource'. @see java.lang.Class.getResourceAsStream() + * + * @param fileName Name of the file + * @param srcDir Where the file is currently located + * @param dstDir Where the file should be placed + * @return The name of the file in destinationDirectory + */ + public static String copyFileOrResource(String fileName, Path srcDir, Path dstDir) { + // Try to copy the file from the file system. + Path file = FileConfig.findFile(fileName, srcDir); + if (file != null) { + Path target = dstDir.resolve(file.getFileName()); + try { + Files.copy(file, target, StandardCopyOption.REPLACE_EXISTING); + return file.getFileName().toString(); + } catch (IOException e) { + // Files has failed to copy the file, possibly since + // it doesn't exist. Will try to find the file as a + // resource before giving up. + } + } + + // Try to copy the file as a resource. + // If this is missing, it should have been previously reported as an error. + try { + String filenameWithoutPath = fileName; + int lastSeparator = fileName.lastIndexOf(File.separator); + if (lastSeparator > 0) { + filenameWithoutPath = fileName.substring(lastSeparator + 1); // FIXME: brittle. What if the file is in a subdirectory? + } + copyFileFromClassPath(fileName, dstDir.resolve(filenameWithoutPath)); + return filenameWithoutPath; + } catch (IOException ex) { + // Ignore. Previously reported as a warning. + System.err.println("WARNING: Failed to find file " + fileName); + } + + return ""; + } + + /** + * Recursively delete a directory if it exists. + * + * @throws IOException If an I/O error occurs. + */ + public static void deleteDirectory(Path dir) throws IOException { + if (Files.isDirectory(dir)) { + System.out.println("Cleaning " + dir); + List pathsToDelete = Files.walk(dir) + .sorted(Comparator.reverseOrder()) + .collect(Collectors.toList()); + for (Path path : pathsToDelete) { + Files.deleteIfExists(path); + } + } + } } From 1bc32107c7239e139a0354795ac68d2eb1b75a72 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 16:31:16 +0100 Subject: [PATCH 16/30] copyUserFiles is only ever used in the CGenerator... --- .../org/lflang/generator/GeneratorBase.xtend | 38 ------------------- .../org/lflang/generator/c/CGenerator.xtend | 30 +++++++++++++-- 2 files changed, 27 insertions(+), 41 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index b736a6ce19..c32780ca53 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -26,7 +26,6 @@ package org.lflang.generator import java.io.File import java.io.IOException; -import java.nio.file.Files import java.nio.file.Paths import java.util.ArrayList import java.util.HashSet @@ -65,7 +64,6 @@ import org.lflang.lf.Reactor import org.lflang.lf.Time import org.lflang.lf.Value import org.lflang.lf.VarRef -import org.lflang.util.FileUtil import static extension org.lflang.ASTUtils.* import org.lflang.validation.AbstractLFValidator @@ -318,12 +316,6 @@ abstract class GeneratorBase extends AbstractLFValidator { // This must be done before desugaring delays below. analyzeFederates(context) - - // Process target files. Copy each of them into the src-gen dir. - // FIXME: Should we do this here? I think the Cpp target doesn't support - // the files property and this doesn't make sense for federates the way it is - // done here. - copyUserFiles(this.targetConfig, this.fileConfig); // Collect reactors and create an instantiation graph. // These are needed to figure out which resources we need @@ -404,36 +396,6 @@ abstract class GeneratorBase extends AbstractLFValidator { } } - /** - * Copy all files listed in the target property `files` into the - * src-gen folder of the main .lf file. - * - * @param targetConfig The targetConfig to read the `files` from. - * @param fileConfig The fileConfig used to make the copy and resolve paths. - */ - protected def copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) { - // Make sure the target directory exists. - val targetDir = this.fileConfig.getSrcGenPath - Files.createDirectories(targetDir) - - for (filename : targetConfig.fileNames) { - val relativeFileName = FileUtil.copyFileOrResource( - filename, - fileConfig.srcFile.parent, - targetDir); - if (relativeFileName.isNullOrEmpty) { - errorReporter.reportError( - "Failed to find file " + filename + " specified in the" + - " files target property." - ) - } else { - this.targetConfig.filesNamesWithoutPath.add( - relativeFileName - ); - } - } - } - /** * Return true if errors occurred in the last call to doGenerate(). * This will return true if any of the reportError methods was called. diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 7b65b6fb45..f8c686da07 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -27,6 +27,7 @@ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package org.lflang.generator.c import java.io.File +import java.nio.file.Files import java.util.ArrayList import java.util.HashSet import java.util.LinkedHashMap @@ -439,6 +440,11 @@ class CGenerator extends GeneratorBase { if (!dir.exists()) dir.mkdirs() dir = fileConfig.binPath.toFile if (!dir.exists()) dir.mkdirs() + + // Process target files. Copy each of them into the src-gen dir. + // FIXME: Should we do this here? This doesn't make sense for federates the way it is + // done here. + copyUserFiles(this.targetConfig, this.fileConfig); targetConfig.compileAdditionalSources.add("ctarget.c"); targetConfig.compileAdditionalSources.add("core" + File.separator + "mixed_radix.c"); @@ -1051,10 +1057,28 @@ class CGenerator extends GeneratorBase { * @param targetConfig The targetConfig to read the `files` and `cmake-include` from. * @param fileConfig The fileConfig used to make the copy and resolve paths. */ - override copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) { - super.copyUserFiles(targetConfig, fileConfig); - + def copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) { + // Make sure the target directory exists. val targetDir = this.fileConfig.getSrcGenPath + Files.createDirectories(targetDir) + + for (filename : targetConfig.fileNames) { + val relativeFileName = FileUtil.copyFileOrResource( + filename, + fileConfig.srcFile.parent, + targetDir); + if (relativeFileName.isNullOrEmpty) { + errorReporter.reportError( + "Failed to find file " + filename + " specified in the" + + " files target property." + ) + } else { + this.targetConfig.filesNamesWithoutPath.add( + relativeFileName + ); + } + } + for (filename : targetConfig.cmakeIncludes) { val relativeCMakeIncludeFileName = FileUtil.copyFileOrResource( From 3dd7033e537783bc0d4f64c42c8e9f1d9f040818 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 16:34:48 +0100 Subject: [PATCH 17/30] move C specific FileUtil functions to CUtil --- org.lflang/src/org/lflang/FileConfig.java | 41 -------- .../org/lflang/generator/c/CGenerator.xtend | 4 +- .../src/org/lflang/generator/c/CUtil.java | 93 +++++++++++++++++++ org.lflang/src/org/lflang/util/FileUtil.java | 48 ---------- 4 files changed, 95 insertions(+), 91 deletions(-) diff --git a/org.lflang/src/org/lflang/FileConfig.java b/org.lflang/src/org/lflang/FileConfig.java index 93a160d0f0..c475924244 100644 --- a/org.lflang/src/org/lflang/FileConfig.java +++ b/org.lflang/src/org/lflang/FileConfig.java @@ -2,7 +2,6 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.function.Consumer; @@ -355,44 +354,4 @@ public static Path findPackageRoot(final Path input, final Consumer prin return p.getParent(); } - /** - * Search for a given file name in the given directory. - * If not found, search in directories in LF_CLASSPATH. - * If there is no LF_CLASSPATH environment variable, use CLASSPATH, - * if it is defined. - * The first file found will be returned. - * - * @param fileName The file name or relative path + file name - * in plain string format - * @param directory String representation of the director to search in. - * @return A Java file or null if not found - */ - public static Path findFile(String fileName, Path directory) { - Path foundFile; - - // Check in local directory - foundFile = directory.resolve(fileName); - if (Files.isRegularFile(foundFile)) { - return foundFile; - } - - // Check in LF_CLASSPATH - // Load all the resources in LF_CLASSPATH if it is set. - String classpathLF = System.getenv("LF_CLASSPATH"); - if (classpathLF == null) { - classpathLF = System.getenv("CLASSPATH"); - } - if (classpathLF != null) { - String[] paths = classpathLF.split(System.getProperty("path.separator")); - for (String path : paths) { - foundFile = Paths.get(path).resolve(fileName); - if (Files.isRegularFile(foundFile)) { - return foundFile; - } - } - } - // Not found. - return null; - } - } diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index f8c686da07..76aa93b872 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -1063,7 +1063,7 @@ class CGenerator extends GeneratorBase { Files.createDirectories(targetDir) for (filename : targetConfig.fileNames) { - val relativeFileName = FileUtil.copyFileOrResource( + val relativeFileName = CUtil.copyFileOrResource( filename, fileConfig.srcFile.parent, targetDir); @@ -1081,7 +1081,7 @@ class CGenerator extends GeneratorBase { for (filename : targetConfig.cmakeIncludes) { val relativeCMakeIncludeFileName = - FileUtil.copyFileOrResource( + CUtil.copyFileOrResource( filename, fileConfig.srcFile.parent, targetDir); diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index 625368dd3c..14b97ce92a 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -26,7 +26,12 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator.c; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -545,6 +550,53 @@ static public String triggerRefNested(PortInstance port, String runtimeIndex, St return reactorRefNested(port.getParent(), runtimeIndex, bankIndex) + "." + port.getName() + "_trigger"; } + /** + * Copy the 'fileName' from the 'srcDirectory' to the 'destinationDirectory'. + * This function has a fallback search mechanism, where if `fileName` is not + * found in the `srcDirectory`, it will try to find `fileName` via the following procedure: + * 1- Search in LF_CLASSPATH. @see findFile() + * 2- Search in CLASSPATH. @see findFile() + * 3- Search for 'fileName' as a resource. + * That means the `fileName` can be '/path/to/class/resource'. @see java.lang.Class.getResourceAsStream() + * + * @param fileName Name of the file + * @param srcDir Where the file is currently located + * @param dstDir Where the file should be placed + * @return The name of the file in destinationDirectory + */ + public static String copyFileOrResource(String fileName, Path srcDir, Path dstDir) { + // Try to copy the file from the file system. + Path file = findFile(fileName, srcDir); + if (file != null) { + Path target = dstDir.resolve(file.getFileName()); + try { + Files.copy(file, target, StandardCopyOption.REPLACE_EXISTING); + return file.getFileName().toString(); + } catch (IOException e) { + // Files has failed to copy the file, possibly since + // it doesn't exist. Will try to find the file as a + // resource before giving up. + } + } + + // Try to copy the file as a resource. + // If this is missing, it should have been previously reported as an error. + try { + String filenameWithoutPath = fileName; + int lastSeparator = fileName.lastIndexOf(File.separator); + if (lastSeparator > 0) { + filenameWithoutPath = fileName.substring(lastSeparator + 1); // FIXME: brittle. What if the file is in a subdirectory? + } + FileUtil.copyFileFromClassPath(fileName, dstDir.resolve(filenameWithoutPath)); + return filenameWithoutPath; + } catch (IOException ex) { + // Ignore. Previously reported as a warning. + System.err.println("WARNING: Failed to find file " + fileName); + } + + return ""; + } + ////////////////////////////////////////////////////// //// FIXME: Not clear what the strategy is with the following inner interface. // The {@code ReportCommandErrors} interface allows the @@ -567,6 +619,47 @@ public interface ReportCommandErrors { void report(String errors); } + /** + * Search for a given file name in the given directory. + * If not found, search in directories in LF_CLASSPATH. + * If there is no LF_CLASSPATH environment variable, use CLASSPATH, + * if it is defined. + * The first file found will be returned. + * + * @param fileName The file name or relative path + file name + * in plain string format + * @param directory String representation of the director to search in. + * @return A Java file or null if not found + */ + public static Path findFile(String fileName, Path directory) { + Path foundFile; + + // Check in local directory + foundFile = directory.resolve(fileName); + if (Files.isRegularFile(foundFile)) { + return foundFile; + } + + // Check in LF_CLASSPATH + // Load all the resources in LF_CLASSPATH if it is set. + String classpathLF = System.getenv("LF_CLASSPATH"); + if (classpathLF == null) { + classpathLF = System.getenv("CLASSPATH"); + } + if (classpathLF != null) { + String[] paths = classpathLF.split(System.getProperty("path.separator")); + for (String path : paths) { + foundFile = Paths.get(path).resolve(fileName); + if (Files.isRegularFile(foundFile)) { + return foundFile; + } + } + } + // Not found. + return null; + } + + /** * Run the custom build command specified with the "build" parameter. * This command is executed in the same directory as the source file. diff --git a/org.lflang/src/org/lflang/util/FileUtil.java b/org.lflang/src/org/lflang/util/FileUtil.java index 10a74c3808..6843515876 100644 --- a/org.lflang/src/org/lflang/util/FileUtil.java +++ b/org.lflang/src/org/lflang/util/FileUtil.java @@ -1,7 +1,6 @@ package org.lflang.util; import java.io.BufferedInputStream; -import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; @@ -334,53 +333,6 @@ public static void copyFilesFromClassPath(String srcDir, Path dstDir, List 0) { - filenameWithoutPath = fileName.substring(lastSeparator + 1); // FIXME: brittle. What if the file is in a subdirectory? - } - copyFileFromClassPath(fileName, dstDir.resolve(filenameWithoutPath)); - return filenameWithoutPath; - } catch (IOException ex) { - // Ignore. Previously reported as a warning. - System.err.println("WARNING: Failed to find file " + fileName); - } - - return ""; - } - /** * Recursively delete a directory if it exists. * From e246f92848d51b2ecb965957f0bc5fa2174fc796 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 17:02:26 +0100 Subject: [PATCH 18/30] move getIResource methods to FileUtil --- org.lflang/src/org/lflang/FileConfig.java | 67 ------------------- .../generator/EclipseErrorReporter.java | 6 +- org.lflang/src/org/lflang/util/FileUtil.java | 37 ++++++++++ 3 files changed, 40 insertions(+), 70 deletions(-) diff --git a/org.lflang/src/org/lflang/FileConfig.java b/org.lflang/src/org/lflang/FileConfig.java index c475924244..a658e798e3 100644 --- a/org.lflang/src/org/lflang/FileConfig.java +++ b/org.lflang/src/org/lflang/FileConfig.java @@ -6,10 +6,7 @@ import java.nio.file.Paths; import java.util.function.Consumer; -import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; @@ -69,14 +66,6 @@ public class FileConfig { */ public final Resource resource; - /** - * If running in an Eclipse IDE, the iResource refers to the - * IFile representing the Lingua Franca program. - * This is the XText view of the file, which is distinct - * from the Eclipse eCore view of the file and the OS view of the file. - */ - public final IResource iResource; - /** * The full path to the file containing the .lf file including the * full filename with the .lf extension. @@ -149,65 +138,9 @@ public FileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchica Path binRoot = outPath.resolve(DEFAULT_BIN_DIR); this.binPath = useHierarchicalBin ? binRoot.resolve(getSubPkgPath(srcPkgPath, srcPath)) : binRoot; - - this.iResource = getIResource(resource); } /** - * Get the iResource corresponding to the provided resource if it can be - * found. - * @throws IOException If the given resource has an invalid URI. - */ - public IResource getIResource(Resource r) throws IOException { - IResource iResource = null; - java.net.URI uri = FileUtil.toPath(r).toFile().toURI(); - if (r.getURI().isPlatform()) { - IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); - IFile[] files = workspaceRoot.findFilesForLocationURI(uri); - if (files != null && files.length > 0 && files[0] != null) { - iResource = files[0]; - } - } else { - // FIXME: find the iResource outside Eclipse - } - return iResource; - } - - /** - * Get the specified path as an Eclipse IResource or, if it is not found, then - * return the iResource for the main file. - * - */ - public IResource getIResource(Path path) { - return getIResource(path.toUri()); - } - - /** - * Get the specified uri as an Eclipse IResource or, if it is not found, then - * return the iResource for the main file. - * For some inexplicable reason, Eclipse uses a mysterious parallel to the file - * system, and when running in EPOCH mode, for some things, you cannot access - * files by referring to their file system location. Instead, you have to refer - * to them relative the workspace root. This is required, for example, when marking - * the file with errors or warnings or when deleting those marks. - * - * @param uri A java.net.uri of the form "file://path". - */ - public IResource getIResource(java.net.URI uri) { - IResource resource = iResource; // Default resource. - // For some peculiar reason known only to Eclipse developers, - // the resource cannot be used directly but has to be converted - // a resource relative to the workspace root. - IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); - - IFile[] files = workspaceRoot.findFilesForLocationURI(uri); - if (files != null && files.length > 0 && files[0] != null) { - resource = files[0]; - } - return resource; - } - - /** * Get the file name of a resource without file extension */ public static String getName(Resource r) throws IOException { diff --git a/org.lflang/src/org/lflang/generator/EclipseErrorReporter.java b/org.lflang/src/org/lflang/generator/EclipseErrorReporter.java index 0429d283ba..922fda06ce 100644 --- a/org.lflang/src/org/lflang/generator/EclipseErrorReporter.java +++ b/org.lflang/src/org/lflang/generator/EclipseErrorReporter.java @@ -114,8 +114,8 @@ private String report(String message, Severity severity, Integer line, Path file // Create a marker in the IDE for the error. // See: https://help.eclipse.org/2020-03/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Fguide%2FresAdv_markers.htm final IResource iResource = file != null - ? fileConfig.getIResource(file) - : fileConfig.iResource; + ? FileUtil.getIResource(file) + : FileUtil.getIResource(fileConfig.resource); try { IMarker marker = iResource.createMarker(IMarker.PROBLEM); @@ -233,7 +233,7 @@ public boolean getErrorsOccurred() { */ public void clearMarkers() { try { - IResource resource = fileConfig.getIResource(fileConfig.srcFile); + IResource resource = FileUtil.getIResource(fileConfig.srcFile); IMarker[] markers = resource.findMarkers(null, true, IResource.DEPTH_INFINITE); for (IMarker marker : markers) { diff --git a/org.lflang/src/org/lflang/util/FileUtil.java b/org.lflang/src/org/lflang/util/FileUtil.java index 6843515876..76a6bac500 100644 --- a/org.lflang/src/org/lflang/util/FileUtil.java +++ b/org.lflang/src/org/lflang/util/FileUtil.java @@ -21,6 +21,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IPath; @@ -349,4 +352,38 @@ public static void deleteDirectory(Path dir) throws IOException { } } } + + /** + * Get the iResource corresponding to the provided resource if it can be + * found. + */ + public static IResource getIResource(Resource r) { + return getIResource(java.net.URI.create(r.getURI().toString())); + } + + /** + * Get the specified path as an Eclipse IResource or null if it is not found. + */ + public static IResource getIResource(Path path) { + return getIResource(path.toUri()); + } + + /** + * Get the specified uri as an Eclipse IResource or null if it is not found. + * + * @param uri A java.net.uri of the form "file://path". + */ + public static IResource getIResource(java.net.URI uri) { + IResource resource = null; + // For some peculiar reason known only to Eclipse developers, + // the resource cannot be used directly but has to be converted + // a resource relative to the workspace root. + IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); + + IFile[] files = workspaceRoot.findFilesForLocationURI(uri); + if (files != null && files.length > 0 && files[0] != null) { + resource = files[0]; + } + return resource; + } } From 4313e3fe7d3829ae131c0e39cfb2d0355d08782f Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 17:09:12 +0100 Subject: [PATCH 19/30] remove redundant getName() --- org.lflang/src/org/lflang/FileConfig.java | 7 ------- org.lflang/src/org/lflang/FileConfigExtensions.kt | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/org.lflang/src/org/lflang/FileConfig.java b/org.lflang/src/org/lflang/FileConfig.java index a658e798e3..fcd2fc01fa 100644 --- a/org.lflang/src/org/lflang/FileConfig.java +++ b/org.lflang/src/org/lflang/FileConfig.java @@ -139,13 +139,6 @@ public FileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchica Path binRoot = outPath.resolve(DEFAULT_BIN_DIR); this.binPath = useHierarchicalBin ? binRoot.resolve(getSubPkgPath(srcPkgPath, srcPath)) : binRoot; } - - /** - * Get the file name of a resource without file extension - */ - public static String getName(Resource r) throws IOException { - return FileUtil.nameWithoutExtension(FileUtil.toPath(r)); - } /** * Get the directory a resource is located in relative to the root package diff --git a/org.lflang/src/org/lflang/FileConfigExtensions.kt b/org.lflang/src/org/lflang/FileConfigExtensions.kt index 6283c5881e..9e2fb4ad14 100644 --- a/org.lflang/src/org/lflang/FileConfigExtensions.kt +++ b/org.lflang/src/org/lflang/FileConfigExtensions.kt @@ -31,7 +31,7 @@ import java.nio.file.Path /** * Get the file name of a resource without file extension */ -val Resource.name: String get() = FileConfig.getName(this) +val Resource.name: String get() = FileUtil.nameWithoutExtension(this) /** Get the path of the receiving resource */ fun Resource.toPath() = FileUtil.toPath(this) From 4e01e55e2ca496b5156c6e12284b6ca6cd023f8a Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 17:12:54 +0100 Subject: [PATCH 20/30] getSubPkgPath should not be static --- org.lflang/src/org/lflang/FileConfig.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/org.lflang/src/org/lflang/FileConfig.java b/org.lflang/src/org/lflang/FileConfig.java index fcd2fc01fa..c644552ecb 100644 --- a/org.lflang/src/org/lflang/FileConfig.java +++ b/org.lflang/src/org/lflang/FileConfig.java @@ -132,19 +132,19 @@ public FileConfig(Resource resource, Path srcGenBasePath, boolean useHierarchica this.srcGenBasePath = srcGenBasePath; this.name = FileUtil.nameWithoutExtension(this.srcFile); - this.srcGenPath = srcGenBasePath.resolve(getSubPkgPath(srcPkgPath, srcPath)).resolve(name); + this.srcGenPath = srcGenBasePath.resolve(getSubPkgPath(srcPath)).resolve(name); this.srcGenPkgPath = this.srcGenPath; this.outPath = srcGenBasePath.getParent(); Path binRoot = outPath.resolve(DEFAULT_BIN_DIR); - this.binPath = useHierarchicalBin ? binRoot.resolve(getSubPkgPath(srcPkgPath, srcPath)) : binRoot; + this.binPath = useHierarchicalBin ? binRoot.resolve(getSubPkgPath(srcPath)) : binRoot; } /** * Get the directory a resource is located in relative to the root package */ public Path getDirectory(Resource r) throws IOException { - return getSubPkgPath(this.srcPkgPath, FileUtil.toPath(r).getParent()); + return getSubPkgPath(FileUtil.toPath(r).getParent()); } /** @@ -203,19 +203,17 @@ public static Path getSrcGenRoot(IFileSystemAccess2 fsa) throws IOException { } /** - * Given a path that denotes the root of the package and a path - * that denotes the full path to a source file (not including the + * Given a path that denotes the full path to a source file (not including the * file itself), return the relative path from the root of the 'src' * directory, or, if there is no 'src' directory, the relative path - * from the root of the package. - * @param pkgPath The root of the package. + * from the root of the package. * @param srcPath The path to the source. * @return the relative path from the root of the 'src' * directory, or, if there is no 'src' directory, the relative path * from the root of the package */ - protected static Path getSubPkgPath(Path pkgPath, Path srcPath) { - Path relSrcPath = pkgPath.relativize(srcPath); + protected Path getSubPkgPath(Path srcPath) { + Path relSrcPath = srcPkgPath.relativize(srcPath); if (relSrcPath.startsWith(DEFAULT_SRC_DIR)) { int segments = relSrcPath.getNameCount(); if (segments == 1) { From 7ae443e52b736ca3e726f0232689fbc68439a2cf Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 18:07:40 +0100 Subject: [PATCH 21/30] fix diagrams --- .../org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java index b1f3fbef70..b0fb856fde 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.java @@ -117,6 +117,7 @@ import org.lflang.lf.Connection; import org.lflang.lf.Model; import org.lflang.lf.Reactor; +import org.lflang.util.FileUtil; /** * Diagram synthesis for Lingua Franca programs. @@ -975,7 +976,7 @@ private String createReactorLabel(ReactorInstance reactorInstance) { } if (reactorInstance.isMainOrFederated()) { try { - b.append(FileConfig.nameWithoutExtension(reactorInstance.reactorDeclaration.eResource())); + b.append(FileUtil.nameWithoutExtension(reactorInstance.reactorDeclaration.eResource())); } catch (Exception e) { throw Exceptions.sneakyThrow(e); } From 76096633709f369e098c99c747760c358c056687 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 18:07:53 +0100 Subject: [PATCH 22/30] files is only used in C, CCpp and Python --- org.lflang/src/org/lflang/TargetProperty.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/TargetProperty.java b/org.lflang/src/org/lflang/TargetProperty.java index 0b8ff64cf2..662ada9a6a 100644 --- a/org.lflang/src/org/lflang/TargetProperty.java +++ b/org.lflang/src/org/lflang/TargetProperty.java @@ -211,7 +211,7 @@ public enum TargetProperty { * Directive to stage particular files on the class path to be * processed by the code generator. */ - FILES("files", UnionType.FILE_OR_FILE_ARRAY, Target.ALL, + FILES("files", UnionType.FILE_OR_FILE_ARRAY, List.of(Target.C, Target.CCPP, Target.Python), (config, value, err) -> { config.fileNames = ASTUtils.toListOfStrings(value); }, From 51e0aadce675a7050e0296b783472071c5806308 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 18:08:15 +0100 Subject: [PATCH 23/30] automatically add the src directory to the C++ includes --- .../src/org/lflang/generator/cpp/CppCmakeGenerator.kt | 1 + org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt | 1 + test/C/src/Minimal.lf | 6 ++++-- test/Cpp/src/StructAsState.lf | 3 +-- test/Cpp/src/StructAsType.lf | 5 ++--- test/Cpp/src/StructAsTypeDirect.lf | 5 ++--- test/Cpp/src/StructParallel.lf | 3 +-- test/Cpp/src/StructPrint.lf | 3 +-- test/Cpp/src/StructScale.lf | 3 +-- 9 files changed, 14 insertions(+), 16 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/cpp/CppCmakeGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppCmakeGenerator.kt index 113a807964..42cec55e63 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppCmakeGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppCmakeGenerator.kt @@ -128,6 +128,7 @@ class CppCmakeGenerator(private val targetConfig: TargetConfig, private val file |) |target_include_directories($S{LF_MAIN_TARGET} PUBLIC | "$S{CMAKE_INSTALL_PREFIX}/$S{CMAKE_INSTALL_INCLUDEDIR}" + | "$S{CMAKE_INSTALL_PREFIX}/src" | "$S{PROJECT_SOURCE_DIR}" | "$S{PROJECT_SOURCE_DIR}/__include__" |) diff --git a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt index 611a473aaf..331297f88c 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt @@ -28,6 +28,7 @@ package org.lflang.generator.cpp import org.eclipse.emf.ecore.resource.Resource import org.lflang.ErrorReporter +import org.lflang.FileConfig import org.lflang.Target import org.lflang.generator.LFGeneratorContext.Mode import org.lflang.TargetProperty diff --git a/test/C/src/Minimal.lf b/test/C/src/Minimal.lf index 76de0c5b9e..77c72371c7 100644 --- a/test/C/src/Minimal.lf +++ b/test/C/src/Minimal.lf @@ -1,8 +1,10 @@ // This is a smoke test of a minimal reactor. -target C; +target C{ + foo: bar +} main reactor Minimal { reaction(startup) {= printf("Hello World.\n"); =} -} \ No newline at end of file +} diff --git a/test/Cpp/src/StructAsState.lf b/test/Cpp/src/StructAsState.lf index 81aa4792fd..808a79e38c 100644 --- a/test/Cpp/src/StructAsState.lf +++ b/test/Cpp/src/StructAsState.lf @@ -1,9 +1,8 @@ // Check that a state variable can be a statically initialized struct target Cpp { - files: include/hello.h }; public preamble {= - #include "hello.h" + #include "include/hello.h" =} main reactor StructAsState { diff --git a/test/Cpp/src/StructAsType.lf b/test/Cpp/src/StructAsType.lf index fd8d9eee4d..f1aef0c7fd 100644 --- a/test/Cpp/src/StructAsType.lf +++ b/test/Cpp/src/StructAsType.lf @@ -1,12 +1,11 @@ // Source produces a statically allocated struct and sends a copy. target Cpp { - files: include/hello.h }; import Print from "StructPrint.lf"; public preamble {= - #include "hello.h" + #include "include/hello.h" =} reactor StaticSource { @@ -22,4 +21,4 @@ main reactor StructAsType { s = new StaticSource(); p = new Print(); s.out -> p.in; -} \ No newline at end of file +} diff --git a/test/Cpp/src/StructAsTypeDirect.lf b/test/Cpp/src/StructAsTypeDirect.lf index f3dde23c3b..44f811890f 100644 --- a/test/Cpp/src/StructAsTypeDirect.lf +++ b/test/Cpp/src/StructAsTypeDirect.lf @@ -1,12 +1,11 @@ // Source directly sends an implicitly dynamically created object target Cpp { - files: include/hello.h }; import Print from "StructPrint.lf"; public preamble {= - #include "hello.h" + #include "include/hello.h" =} reactor DirectSource { @@ -21,4 +20,4 @@ main reactor { s = new DirectSource(); p = new Print(); s.out -> p.in; -} \ No newline at end of file +} diff --git a/test/Cpp/src/StructParallel.lf b/test/Cpp/src/StructParallel.lf index 993e876273..5c8c0df9fe 100644 --- a/test/Cpp/src/StructParallel.lf +++ b/test/Cpp/src/StructParallel.lf @@ -3,13 +3,12 @@ // NOTE: Ideally, only one copy would be made, but this requires // is currently not supported by the C++ runtime. target Cpp { - files: include/hello.h }; import Scale from "StructScale.lf"; import Source, Print from "StructPrint.lf" public preamble {= - #include "hello.h" + #include "include/hello.h" =} main reactor { diff --git a/test/Cpp/src/StructPrint.lf b/test/Cpp/src/StructPrint.lf index b4f0c37d1f..525e475cad 100644 --- a/test/Cpp/src/StructPrint.lf +++ b/test/Cpp/src/StructPrint.lf @@ -1,9 +1,8 @@ // Source directly sends an implicitly dynamically created object target Cpp { - files: include/hello.h }; public preamble {= - #include "hello.h" + #include "include/hello.h" =} reactor Source { diff --git a/test/Cpp/src/StructScale.lf b/test/Cpp/src/StructScale.lf index 46f9ff8d6c..1e895f6737 100644 --- a/test/Cpp/src/StructScale.lf +++ b/test/Cpp/src/StructScale.lf @@ -3,13 +3,12 @@ // It modifies it and passes it to Print. It gets freed after // Print is done with it. target Cpp { - files: include/hello.h }; import Source, Print from "StructPrint.lf"; public preamble {= - #include "hello.h" + #include "include/hello.h" =} reactor Scale(scale:int(2)) { From 98f8abb41291e6879e52fa3f4b06daf4a79b357e Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 18:50:27 +0100 Subject: [PATCH 24/30] copy user files before invoking proto-c --- .../src/org/lflang/generator/GeneratorBase.xtend | 15 +++++++++++++++ .../src/org/lflang/generator/c/CGenerator.xtend | 8 ++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index c32780ca53..ffe0507fb5 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -317,6 +317,11 @@ abstract class GeneratorBase extends AbstractLFValidator { // This must be done before desugaring delays below. analyzeFederates(context) + // Process target files. Copy each of them into the src-gen dir. + // FIXME: Should we do this here? This doesn't make sense for federates the way it is + // done here. + //copyUserFiles(this.targetConfig, this.fileConfig); + // Collect reactors and create an instantiation graph. // These are needed to figure out which resources we need // to validate, which happens in setResources(). @@ -396,6 +401,16 @@ abstract class GeneratorBase extends AbstractLFValidator { } } + /** + * Copy user specific files to the src-gen folder. + * + * This should be overridden by the target generators. + * + * @param targetConfig The targetConfig to read the `files` from. + * @param fileConfig The fileConfig used to make the copy and resolve paths. + */ + protected def void copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) {} + /** * Return true if errors occurred in the last call to doGenerate(). * This will return true if any of the reportError methods was called. diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 76aa93b872..ffe3fa4574 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -440,11 +440,6 @@ class CGenerator extends GeneratorBase { if (!dir.exists()) dir.mkdirs() dir = fileConfig.binPath.toFile if (!dir.exists()) dir.mkdirs() - - // Process target files. Copy each of them into the src-gen dir. - // FIXME: Should we do this here? This doesn't make sense for federates the way it is - // done here. - copyUserFiles(this.targetConfig, this.fileConfig); targetConfig.compileAdditionalSources.add("ctarget.c"); targetConfig.compileAdditionalSources.add("core" + File.separator + "mixed_radix.c"); @@ -1057,7 +1052,8 @@ class CGenerator extends GeneratorBase { * @param targetConfig The targetConfig to read the `files` and `cmake-include` from. * @param fileConfig The fileConfig used to make the copy and resolve paths. */ - def copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) { + override copyUserFiles(TargetConfig targetConfig, FileConfig fileConfig) { + super.copyUserFiles(targetConfig, fileConfig) // Make sure the target directory exists. val targetDir = this.fileConfig.getSrcGenPath Files.createDirectories(targetDir) From 7f30061ae5ad5b59249353ab98566edfecbb4f3a Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Fri, 25 Feb 2022 19:01:54 +0100 Subject: [PATCH 25/30] uncomment copyUserFiles --- org.lflang/src/org/lflang/generator/GeneratorBase.xtend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index ffe0507fb5..76d255fd93 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -320,7 +320,7 @@ abstract class GeneratorBase extends AbstractLFValidator { // Process target files. Copy each of them into the src-gen dir. // FIXME: Should we do this here? This doesn't make sense for federates the way it is // done here. - //copyUserFiles(this.targetConfig, this.fileConfig); + copyUserFiles(this.targetConfig, this.fileConfig); // Collect reactors and create an instantiation graph. // These are needed to figure out which resources we need From 21ab9a9b4518be2b82dc92e57559634c09486b08 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Sat, 26 Feb 2022 11:27:58 +0100 Subject: [PATCH 26/30] revert an accidental change --- test/C/src/Minimal.lf | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/C/src/Minimal.lf b/test/C/src/Minimal.lf index 77c72371c7..3fbd9cc948 100644 --- a/test/C/src/Minimal.lf +++ b/test/C/src/Minimal.lf @@ -1,7 +1,5 @@ // This is a smoke test of a minimal reactor. -target C{ - foo: bar -} +target C; main reactor Minimal { reaction(startup) {= printf("Hello World.\n"); From 6d9168aeb87ed4f9dc2c26d34a8a70035967bab3 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Mon, 28 Feb 2022 08:53:09 +0100 Subject: [PATCH 27/30] port changes to the Python generator --- .../lflang/generator/JavaGeneratorUtils.java | 4 +- .../org/lflang/generator/python/PyUtil.java | 15 +- .../generator/python/PythonGenerator.java | 14 +- .../generator/python/PythonGenerator.xtend | 2141 ----------------- 4 files changed, 15 insertions(+), 2159 deletions(-) delete mode 100644 org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend diff --git a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java index f4c3d76573..585bbc4924 100644 --- a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java +++ b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -36,6 +37,7 @@ import org.lflang.lf.Model; import org.lflang.lf.Reactor; import org.lflang.lf.TargetDecl; +import org.lflang.util.FileUtil; /** * A helper class with functions that may be useful for code @@ -235,7 +237,7 @@ public static void validate( // Report the error on this resource. Path path = null; try { - path = FileConfig.toPath(resource); + path = FileUtil.toPath(resource); } catch (IOException e) { path = Paths.get("Unknown file"); // Not sure if this is what we want. } diff --git a/org.lflang/src/org/lflang/generator/python/PyUtil.java b/org.lflang/src/org/lflang/generator/python/PyUtil.java index ff65be5ce6..b75cabf960 100644 --- a/org.lflang/src/org/lflang/generator/python/PyUtil.java +++ b/org.lflang/src/org/lflang/generator/python/PyUtil.java @@ -32,9 +32,11 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Value; import java.io.IOException; +import java.nio.file.Path; import org.lflang.ASTUtils; import org.lflang.FileConfig; +import org.lflang.util.FileUtil; /** @@ -175,17 +177,18 @@ protected static String getPythonTargetValue(Value v) { public static void copyTargetFiles(FileConfig fileConfig) throws IOException { // Copy the required target language files into the target file system. // This will also overwrite previous versions. - fileConfig.copyFileFromClassPath( + final Path srcGen = fileConfig.getSrcGenPath(); + FileUtil.copyFileFromClassPath( "/lib/py/reactor-c-py/include/pythontarget.h", - fileConfig.getSrcGenPath().resolve("pythontarget.h") + srcGen.resolve("pythontarget.h") ); - fileConfig.copyFileFromClassPath( + FileUtil.copyFileFromClassPath( "/lib/py/reactor-c-py/lib/pythontarget.c", - fileConfig.getSrcGenPath().resolve("pythontarget.c") + srcGen.resolve("pythontarget.c") ); - fileConfig.copyFileFromClassPath( + FileUtil.copyFileFromClassPath( "/lib/c/reactor-c/include/ctarget.h", - fileConfig.getSrcGenPath().resolve("ctarget.h") + srcGen.resolve("ctarget.h") ); } } \ No newline at end of file diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java index 4c6cf4be18..b21e2f9d45 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java @@ -50,7 +50,6 @@ import org.lflang.InferredType; import org.lflang.JavaAstUtils; import org.lflang.Target; -import org.lflang.TargetConfig.Mode; import org.lflang.federated.FedFileConfig; import org.lflang.federated.FederateInstance; import org.lflang.federated.launcher.FedPyLauncher; @@ -78,6 +77,7 @@ import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; import org.lflang.lf.VarRef; +import org.lflang.util.FileUtil; import org.lflang.util.LFCommand; import com.google.common.base.Objects; @@ -176,14 +176,6 @@ public Target getTarget() { // ////////////////////////////////////////// // // Public methods - @Override - public String printInfo() { - System.out.println("Generating code for: " + fileConfig.resource.getURI().toString()); - System.out.println("******** Mode: " + fileConfig.context.getMode()); - System.out.println("******** Generated sources: " + fileConfig.getSrcGenPath()); - return null; - } - @Override public TargetTypes getTargetTypes() { return types; @@ -262,7 +254,7 @@ public String generatePythonSetupFile() { sources.add(topLevelName + ".c"); sources = sources.stream() .map(Paths::get) - .map(FileConfig::toUnixString) + .map(FileUtil::toUnixString) .map(PythonGenerator::addDoubleQuotes) .collect(Collectors.toList()); @@ -712,7 +704,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { ); // If there are no federates, compile and install the generated code new PythonValidator(fileConfig, errorReporter, codeMaps, protoNames).doValidate(context); - if (!errorsOccurred() && !Objects.equal(context.getMode(), Mode.LSP_MEDIUM)) { + if (!errorsOccurred() && !Objects.equal(context.getMode(), LFGeneratorContext.Mode.LSP_MEDIUM)) { compilingFederatesContext.reportProgress( String.format("Validation complete. Compiling and installing %d/%d Python modules...", federateCount, federates.size()), diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend deleted file mode 100644 index b1f74ff2b1..0000000000 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ /dev/null @@ -1,2141 +0,0 @@ -/* Generator for the Python target. */ - -/************* - * Copyright (c) 2022, The University of California at Berkeley. - * Copyright (c) 2022, The University of Texas at Dallas. - - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL - * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF - * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ***************/ -package org.lflang.generator.python - -import java.io.File -import java.nio.file.Path -import java.util.ArrayList -import java.util.HashMap -import java.util.HashSet -import java.util.LinkedHashSet -import java.util.LinkedList -import java.util.List -import org.eclipse.emf.ecore.resource.Resource -import org.eclipse.xtext.util.CancelIndicator -import org.lflang.ErrorReporter -import org.lflang.FileConfig -import org.lflang.InferredType -import org.lflang.JavaAstUtils -import org.lflang.Target -import org.lflang.TargetProperty.CoordinationType -import org.lflang.federated.FedFileConfig -import org.lflang.federated.FederateInstance -import org.lflang.federated.PythonGeneratorExtension -import org.lflang.federated.launcher.FedPyLauncher -import org.lflang.federated.serialization.FedNativePythonSerialization -import org.lflang.federated.serialization.SupportedSerializers -import org.lflang.generator.CodeBuilder -import org.lflang.generator.CodeMap -import org.lflang.generator.GeneratorResult -import org.lflang.generator.IntegratedBuilder -import org.lflang.generator.JavaGeneratorUtils -import org.lflang.generator.LFGeneratorContext -import org.lflang.generator.ParameterInstance -import org.lflang.generator.ReactionInstance -import org.lflang.generator.ReactorInstance -import org.lflang.generator.SubContext -import org.lflang.generator.c.CGenerator -import org.lflang.generator.c.CUtil -import org.lflang.lf.Action -import org.lflang.lf.Assignment -import org.lflang.lf.Delay -import org.lflang.lf.Input -import org.lflang.lf.Instantiation -import org.lflang.lf.Model -import org.lflang.lf.Output -import org.lflang.lf.Parameter -import org.lflang.lf.Port -import org.lflang.lf.Reaction -import org.lflang.lf.Reactor -import org.lflang.lf.ReactorDecl -import org.lflang.lf.StateVar -import org.lflang.lf.TriggerRef -import org.lflang.lf.Value -import org.lflang.lf.VarRef -import org.lflang.util.FileUtil - -import static extension org.lflang.ASTUtils.* -import static extension org.lflang.JavaAstUtils.* - -/** - * Generator for Python target. This class generates Python code defining each reactor - * class given in the input .lf file and imported .lf files. - * - * Each class will contain all the reaction functions defined by the user in order, with the necessary ports/actions given as parameters. - * Moreover, each class will contain all state variables in native Python format. - * - * A backend is also generated using the CGenerator that interacts with the C code library (see CGenerator.xtend). - * The backend is responsible for passing arguments to the Python reactor functions. - * - * @author{Soroush Bateni } - */ -class PythonGenerator extends CGenerator { - - // Used to add statements that come before reactor classes and user code - var pythonPreamble = new StringBuilder() - - // Used to add module requirements to setup.py (delimited with ,) - var pythonRequiredModules = new StringBuilder() - - var PythonTypes types; - - new(FileConfig fileConfig, ErrorReporter errorReporter) { - this(fileConfig, errorReporter, new PythonTypes(errorReporter)) - } - - private new(FileConfig fileConfig, ErrorReporter errorReporter, PythonTypes types) { - super(fileConfig, errorReporter, false, types) - // set defaults - targetConfig.compiler = "gcc" - targetConfig.compilerFlags = newArrayList // -Wall -Wconversion" - targetConfig.linkerFlags = "" - this.types = types - } - - /** - * Generic struct for ports with primitive types and - * statically allocated arrays in Lingua Franca. - * This template is defined as - * typedef struct { - * PyObject* value; - * bool is_present; - * int num_destinations; - * FEDERATED_CAPSULE_EXTENSION - * } generic_port_instance_struct; - * - * @see reactor-c-py/lib/pythontarget.h - */ - val generic_port_type = "generic_port_instance_struct" - - /** - * Generic struct for ports with dynamically allocated - * array types (a.k.a. token types) in Lingua Franca. - * This template is defined as - * typedef struct { - * PyObject_HEAD - * PyObject* value; - * bool is_present; - * int num_destinations; - * lf_token_t* token; - * int length; - * FEDERATED_CAPSULE_EXTENSION - * } generic_port_instance_with_token_struct; - * - * @see reactor-c-py/lib/pythontarget.h - */ - val generic_port_type_with_token = "generic_port_instance_with_token_struct" - - /** - * Generic struct for actions. - * This template is defined as - * typedef struct { - * trigger_t* trigger; - * PyObject* value; - * bool is_present; - * bool has_value; - * lf_token_t* token; - * FEDERATED_CAPSULE_EXTENSION - * } generic_action_instance_struct; - * - * @see reactor-c-py/lib/pythontarget.h - */ - val generic_action_type = "generic_action_instance_struct" - - /** Returns the Target enum for this generator */ - override getTarget() { - return Target.Python - } - - val protoNames = new HashSet() - - // ////////////////////////////////////////// - /** - * Print information about necessary steps to install the supporting - * Python C extension for the generated program. - * - * @note Only needed if no-compile is set to true - */ - def printSetupInfo() { - println(''' - - ##################################### - To compile and install the generated code, do: - - cd «fileConfig.srcGenPath»«File.separator» - python3 -m pip install --force-reinstall . - '''); - } - - /** - * Print information on how to execute the generated program. - */ - def printRunInfo() { - println(''' - - ##################################### - To run the generated program, use: - - python3 «fileConfig.srcGenPath»«File.separator»«topLevelName».py - - ##################################### - '''); - } - - /** - * Print information on how to execute the generated federation. - */ - def printFedRunInfo() { - println(''' - - ##################################### - To run the generated program, run: - - bash «fileConfig.binPath»/«fileConfig.name» - - ##################################### - '''); - } - - override getTargetTypes() { - return types; - } - - // ////////////////////////////////////////// - // // Protected methods - /** - * Override to convert some C types to their - * Python equivalent. - * Examples: - * true/false -> True/False - * @param v A value - * @return A value string in the target language - */ - private def getPythonTargetValue(Value v) { - var String returnValue = ""; - switch (v.toText) { - case "false": returnValue = "False" - case "true": returnValue = "True" - default: returnValue = v.targetValue - } - - // Parameters in Python are always prepended with a 'self.' - // predicate. Therefore, we need to append the returned value - // if it is a parameter. - if (v.parameter !== null) { - returnValue = "self." + returnValue; - } - - return returnValue; - } - - /** - * Create a list of state initializers in target code. - * - * @param state The state variable to create initializers for - * @return A list of initializers in target code - */ - protected def List getPythonInitializerList(StateVar state) { - if (!state.isInitialized) { - return null - } - - var list = new ArrayList(); - - for (i : state?.init) { - if (i.parameter !== null) { - list.add(i.parameter.name) - } else if (state.isOfTimeType) { - list.add(i.targetTime) - } else { - list.add(i.pythonTargetValue) - } - } - return list - } - - /** - * Create a Python tuple for parameter initialization in target code. - * - * @param p The parameter instance to create initializers for - * @return Initialization code - */ - protected def String getPythonInitializer(StateVar state) throws Exception { - if (state.init.size > 1) { - // state variables are initialized as mutable lists - return state.init.join('[', ', ', ']', [it.pythonTargetValue]) - } else if (state.isInitialized) { - return state.init.get(0).getPythonTargetValue - } else { - return "None" - } - - } - - /** - * Return a Python expression that can be used to initialize the specified - * parameter instance. If the parameter initializer refers to other - * parameters, then those parameter references are replaced with - * accesses to the Python reactor instance class of the parents of - * those parameters. - * - * @param p The parameter instance to create initializer for - * @return Initialization code - */ - protected def String getPythonInitializer(ParameterInstance p) { - // Handle overrides in the intantiation. - // In case there is more than one assignment to this parameter, we need to - // find the last one. - var lastAssignment = null as Assignment; - for (assignment : p.parent.definition.parameters) { - if (assignment.lhs == p.definition) { - lastAssignment = assignment; - } - } - - var list = new LinkedList(); - if (lastAssignment !== null) { - // The parameter has an assignment. - // Right hand side can be a list. Collect the entries. - for (value : lastAssignment.rhs) { - if (value.parameter !== null) { - // The parameter is being assigned a parameter value. - // Assume that parameter belongs to the parent's parent. - // This should have been checked by the validator. - list.add(PyUtil.reactorRef(p.parent.parent) + "." + value.parameter.name); - } else { - list.add(value.targetTime) - } - } - } else { - for (i : p.parent.initialParameterValue(p.definition)) { - list.add(i.getPythonTargetValue) - } - } - - if (list.size == 1) { - return list.get(0) - } else { - return list.join('(', ', ', ')', [it]) - } - - } - - /** - * Create a Python list for parameter initialization in target code. - * - * @param p The parameter to create initializers for - * @return Initialization code - */ - protected def String getPythonInitializer(Parameter p) { - if (p.init.size > 1) { - // parameters are initialized as immutable tuples - return p.init.join('(', ', ', ')', [it.pythonTargetValue]) - } else { - return p.init.get(0).pythonTargetValue - } - } - - /** - * Generate parameters and their respective initialization code for a reaction function - * The initialization code is put at the beginning of the reaction before user code - * @param parameters The parameters used for function definition - * @param inits The initialization code for those paramters - * @param decl Reactor declaration - * @param reaction The reaction to be used to generate parameters for - */ - def generatePythonReactionParametersAndInitializations(StringBuilder parameters, CodeBuilder inits, - ReactorDecl decl, Reaction reaction) { - val reactor = decl.toDefinition - var generatedParams = new LinkedHashSet() - - // Handle triggers - for (TriggerRef trigger : reaction.triggers ?: emptyList) { - if (trigger instanceof VarRef) { - if (trigger.variable instanceof Port) { - if (trigger.variable instanceof Input) { - if ((trigger.variable as Input).isMutable) { - generatedParams.add('''mutable_«trigger.variable.name»''') - - // Create a deep copy - if (JavaAstUtils.isMultiport(trigger.variable as Input)) { - inits. - pr('''«trigger.variable.name» = [Make() for i in range(len(mutable_«trigger.variable.name»))]''') - inits.pr('''for i in range(len(mutable_«trigger.variable.name»)):''') - inits. - pr(''' «trigger.variable.name»[i].value = copy.deepcopy(mutable_«trigger.variable.name»[i].value)''') - } else { - inits.pr('''«trigger.variable.name» = Make()''') - inits. - pr('''«trigger.variable.name».value = copy.deepcopy(mutable_«trigger.variable.name».value)''') - } - } else { - generatedParams.add(trigger.variable.name) - } - } else { - // Handle contained reactors' ports - generatedParams.add('''«trigger.container.name»_«trigger.variable.name»''') - generatePythonPortVariableInReaction(trigger, inits) - } - - } else if (trigger.variable instanceof Action) { - generatedParams.add(trigger.variable.name) - } - } - } - - // Handle non-triggering inputs - if (reaction.triggers === null || reaction.triggers.size === 0) { - for (input : reactor.inputs ?: emptyList) { - generatedParams.add(input.name) - if (input.isMutable) { - // Create a deep copy - inits.pr('''«input.name» = copy.deepcopy(«input.name»)''') - } - } - } - for (src : reaction.sources ?: emptyList) { - if (src.variable instanceof Output) { - // Output of a contained reactor - generatedParams.add('''«src.container.name»_«src.variable.name»''') - generatePythonPortVariableInReaction(src, inits) - } else { - generatedParams.add(src.variable.name) - if (src.variable instanceof Input) { - if ((src.variable as Input).isMutable) { - // Create a deep copy - inits.pr('''«src.variable.name» = copy.deepcopy(«src.variable.name»)''') - } - } - } - } - - // Handle effects - for (effect : reaction.effects ?: emptyList) { - if (effect.variable instanceof Input) { - generatedParams.add('''«effect.container.name»_«effect.variable.name»''') - generatePythonPortVariableInReaction(effect, inits) - } else { - generatedParams.add(effect.variable.name) - if (effect.variable instanceof Port) { - if (JavaAstUtils.isMultiport(effect.variable as Port)) { - // Handle multiports - } - } - } - } - - // Fill out the StrinBuilder parameters - for (s : generatedParams) { - parameters.append(''', «s»''') - } - - } - - /** - * Generate into the specified string builder (inits) the code to - * initialize local variable for port so that it can be used in the body of - * the Python reaction. - * @param port The port to generate code for. - * @param inits The generated code will be put in inits. - */ - protected def CodeBuilder generatePythonPortVariableInReaction(VarRef port, CodeBuilder inits) { - if (port.container.widthSpec !== null) { - // It's a bank - inits.pr(''' - «port.container.name» = [None] * len(«port.container.name»_«port.variable.name») - for i in range(len(«port.container.name»_«port.variable.name»)): - «port.container.name»[i] = Make() - «port.container.name»[i].«port.variable.name» = «port.container.name»_«port.variable.name»[i] - ''') - - } else { - inits.pr('''«port.container.name» = Make''') - inits.pr('''«port.container.name».«port.variable.name» = «port.container.name»_«port.variable.name»''') - } - - return inits; - } - - /** - * Handle initialization for state variable - * @param state a state variable - */ - def String getTargetInitializer(StateVar state) { - if (!state.isInitialized) { - return '''None''' - } - - '''«FOR init : state.pythonInitializerList SEPARATOR ", "»«init»«ENDFOR»''' - } - - /** - * Wrapper function for the more elaborate generatePythonReactorClass that keeps track - * of visited reactors to avoid duplicate generation - * @param instance The reactor instance to be generated - * @param pythonClasses The class definition is appended to this code builder - * @param federate The federate instance for the reactor instance - * @param instantiatedClasses A list of visited instances to avoid generating duplicates - */ - def generatePythonReactorClass(ReactorInstance instance, CodeBuilder pythonClasses, FederateInstance federate) { - var instantiatedClasses = new ArrayList() - generatePythonReactorClass(instance, pythonClasses, federate, instantiatedClasses) - } - - /** - * Generate a Python class corresponding to decl - * @param instance The reactor instance to be generated - * @param pythonClasses The class definition is appended to this code builder - * @param federate The federate instance for the reactor instance - * @param instantiatedClasses A list of visited instances to avoid generating duplicates - */ - def void generatePythonReactorClass(ReactorInstance instance, CodeBuilder pythonClasses, - FederateInstance federate, ArrayList instantiatedClasses) { - if (instance !== this.main && !federate.contains(instance)) { - return - } - - // Invalid use of the function - if (instantiatedClasses === null) { - return - } - - val decl = instance.definition.reactorClass - val className = instance.definition.reactorClass.name - - // Do not generate code for delay reactors in Python - if (className.contains(GEN_DELAY_CLASS_NAME)) { - return - } - - if (federate.contains(instance) && !instantiatedClasses.contains(className)) { - - pythonClasses.pr(''' - - # Python class for reactor «className» - class _«className»: - '''); - - // Generate preamble code - pythonClasses.indent() - pythonClasses.pr(''' - - «generatePythonPreamblesForReactor(decl.toDefinition)» - ''') - - val reactor = decl.toDefinition - - // Handle runtime initializations - pythonClasses.pr(''' - def __init__(self, **kwargs): - ''') - - pythonClasses.pr(generateParametersAndStateVariables(decl)) - - var reactionToGenerate = reactor.allReactions - - if (reactor.isFederated) { - // Filter out reactions that are automatically generated in C in the top level federated reactor - reactionToGenerate.removeIf([ - if (!federate.contains(it)) return true; - if (federate.networkReactions.contains(it)) return true - return false - - ]) - } - - var reactionIndex = 0 - for (reaction : reactionToGenerate) { - val reactionParameters = new StringBuilder() // Will contain parameters for the function (e.g., Foo(x,y,z,...) - val inits = new CodeBuilder() // Will contain initialization code for some parameters - generatePythonReactionParametersAndInitializations(reactionParameters, inits, reactor, reaction) - pythonClasses.pr('''def «pythonReactionFunctionName(reactionIndex)»(self«reactionParameters»):''') - pythonClasses.indent() - pythonClasses.pr(inits); - pythonClasses.pr(reaction.code.toText) - pythonClasses.pr('''return 0''') - pythonClasses.pr(""); - pythonClasses.unindent() - - // Now generate code for the deadline violation function, if there is one. - if (reaction.deadline !== null) { - pythonClasses. - pr('''«generateDeadlineFunctionForReaction(reaction, reactionIndex, reactionParameters.toString)»''') - } - - reactionIndex = reactionIndex + 1; - } - - pythonClasses.unindent() - instantiatedClasses.add(className) - } - - for (child : instance.children) { - generatePythonReactorClass(child, pythonClasses, federate, instantiatedClasses) - } - } - - /** - * Generate code that instantiates and initializes parameters and state variables for a reactor 'decl'. - * - * @param decl The reactor declaration - * @return The generated code as a StringBuilder - */ - protected def CodeBuilder generateParametersAndStateVariables(ReactorDecl decl) { - val reactor = decl.toDefinition - var CodeBuilder temporary_code = new CodeBuilder() - - temporary_code.indent(); - - temporary_code.pr('''#Define parameters and their default values - ''') - - for (param : decl.toDefinition.allParameters) { - if (!types.getTargetType(param).equals("PyObject*")) { - // If type is given, use it - temporary_code. - pr('''self._«param.name»:«types.getPythonType(param.inferredType)» = «param.pythonInitializer» - ''') - } else { - // If type is not given, just pass along the initialization - temporary_code.pr('''self._«param.name» = «param.pythonInitializer» - ''') - - } - } - - // Handle parameters that are set in instantiation - temporary_code.pr('''# Handle parameters that are set in instantiation - ''') - temporary_code.pr('''self.__dict__.update(kwargs) - - ''') - - temporary_code.pr('''# Define state variables - ''') - // Next, handle state variables - for (stateVar : reactor.allStateVars) { - if (stateVar.isInitialized) { - // If initialized, pass along the initialization directly if it is present - temporary_code.pr('''self.«stateVar.name» = «stateVar.pythonInitializer» - ''') - } else { - // If neither the type nor the initialization is given, use None - temporary_code.pr('''self.«stateVar.name» = None - ''') - } - } - - - temporary_code.pr(''' - - ''') - - temporary_code.unindent(); - - // Next, create getters for parameters - for (param : decl.toDefinition.allParameters) { - if (!param.name.equals("bank_index")) { - temporary_code.pr('''@property - ''') - temporary_code.pr('''def «param.name»(self): - ''') - temporary_code.pr(''' return self._«param.name» # pylint: disable=no-member - - ''') - } - } - - // Create a special property for bank_index - temporary_code.pr('''@property - ''') - temporary_code.pr('''def bank_index(self): - ''') - temporary_code.pr(''' return self._bank_index # pylint: disable=no-member - - ''') - - return temporary_code; - } - - /** - * Generate the function that is executed whenever the deadline of the reaction - * with the given reaction index is missed - * @param reaction The reaction to generate deadline miss code for - * @param reactionIndex The agreed-upon index of the reaction in the reactor (should match the C generated code) - * @param reactionParameters The parameters to the deadline violation function, which are the same as the reaction function - */ - def generateDeadlineFunctionForReaction(Reaction reaction, int reactionIndex, String reactionParameters) ''' - «val deadlineFunctionName = 'deadline_function_' + reactionIndex» - - def «deadlineFunctionName»(self «reactionParameters»): - «reaction.deadline.code.toText» - return 0 - - ''' - - /** - * Generates preambles defined by user for a given reactor. - * The preamble code is put inside the reactor class. - */ - def generatePythonPreamblesForReactor(Reactor reactor) ''' - «FOR p : reactor.preambles ?: emptyList» - # From the preamble, verbatim: - «p.code.toText» - # End of preamble. - «ENDFOR» - ''' - - /** - * Instantiate classes in Python. - * Instances are always instantiated as a list of className = [_className, _className, ...] depending on the size of the bank. - * If there is no bank or the size is 1, the instance would be generated as className = [_className] - * @param instance The reactor instance to be instantiated - * @param pythonClassesInstantiation The class instantiations are appended to this code builder - * @param federate The federate instance for the reactor instance - */ - def void generatePythonClassInstantiation(ReactorInstance instance, CodeBuilder pythonClassesInstantiation, - FederateInstance federate) { - // If this is not the main reactor and is not in the federate, nothing to do. - if (instance !== this.main && !federate.contains(instance)) { - return - } - - val className = instance.definition.reactorClass.name - - // Do not instantiate delay reactors in Python - if (className.contains(GEN_DELAY_CLASS_NAME)) { - return - } - - if (federate.contains(instance) && instance.width > 0) { - // For each reactor instance, create a list regardless of whether it is a bank or not. - // Non-bank reactor instances will be a list of size 1. var reactorClass = instance.definition.reactorClass - var fullName = instance.fullName - pythonClassesInstantiation.pr( ''' - - # Start initializing «fullName» of class «className» - for «PyUtil.bankIndexName(instance)» in range(«instance.width»): - ''') - pythonClassesInstantiation.indent(); - pythonClassesInstantiation.pr(''' - «PyUtil.reactorRef(instance)» = \ - _«className»( - _bank_index = «PyUtil.bankIndex(instance)», - «FOR param : instance.parameters» - «IF !param.name.equals("bank_index")» - _«param.name»=«param.pythonInitializer», - «ENDIF»«ENDFOR» - ) - ''') - } - - for (child : instance.children) { - generatePythonClassInstantiation(child, pythonClassesInstantiation, federate) - } - pythonClassesInstantiation.unindent(); - } - - /** - * Generate code to instantiate a Python list that will hold the Python - * class instance of reactor instance. Will recursively do - * the same for the children of instance as well. - * - * @param instance The reactor instance for which the Python list will be created. - * @param pythonClassesInstantiation StringBuilder to hold the generated code. - * @param federate Will check if instance (or any of its children) belong to - * federate before generating code for them. - */ - def void generateListsToHoldClassInstances( - ReactorInstance instance, - CodeBuilder pythonClassesInstantiation, - FederateInstance federate - ) { - if(federate !== null && !federate.contains(instance)) return; - pythonClassesInstantiation.pr(''' - «PyUtil.reactorRefName(instance)» = [None] * «instance.totalWidth» - ''') - for (child : instance.children) { - generateListsToHoldClassInstances(child, pythonClassesInstantiation, federate); - } - } - - /** - * Generate all Python classes if they have a reaction - * @param federate The federate instance used to generate classes - */ - def generatePythonReactorClasses(FederateInstance federate) { - - var CodeBuilder pythonClasses = new CodeBuilder() - var CodeBuilder pythonClassesInstantiation = new CodeBuilder() - - // Generate reactor classes in Python - this.main.generatePythonReactorClass(pythonClasses, federate) - - // Create empty lists to hold reactor instances - this.main.generateListsToHoldClassInstances(pythonClassesInstantiation, federate) - - // Instantiate generated classes - this.main.generatePythonClassInstantiation(pythonClassesInstantiation, federate) - - '''«pythonClasses» - - ''' + '''# Instantiate classes - ''' + '''«pythonClassesInstantiation» - ''' - } - - /** - * Generate the Python code constructed from reactor classes and user-written classes. - * @return the code body - */ - def generatePythonCode(FederateInstance federate) ''' - # List imported names, but do not use pylint's --extension-pkg-allow-list option - # so that these names will be assumed present without having to compile and install. - from LinguaFranca«topLevelName» import ( # pylint: disable=no-name-in-module - Tag, action_capsule_t, compare_tags, get_current_tag, get_elapsed_logical_time, - get_elapsed_physical_time, get_logical_time, get_microstep, get_physical_time, - get_start_time, port_capsule, port_instance_token, request_stop, schedule_copy, - start - ) - from LinguaFrancaBase.constants import BILLION, FOREVER, NEVER, instant_t, interval_t - from LinguaFrancaBase.functions import ( - DAY, DAYS, HOUR, HOURS, MINUTE, MINUTES, MSEC, MSECS, NSEC, NSECS, SEC, SECS, USEC, - USECS, WEEK, WEEKS - ) - from LinguaFrancaBase.classes import Make - import sys - import copy - - «pythonPreamble.toString» - - «generatePythonReactorClasses(federate)» - - «PythonMainGenerator.generateCode()» - ''' - - /** - * Generate the setup.py required to compile and install the module. - * Currently, the package name is based on filename which does not support sharing the setup.py for multiple .lf files. - * TODO: use an alternative package name (possibly based on folder name) - * - * If the LF program itself is threaded or if tracing is enabled, NUMBER_OF_WORKERS is added as a macro - * so that platform-specific C files will contain the appropriate functions. - */ - def generatePythonSetupFile() ''' - from setuptools import setup, Extension - - linguafranca«topLevelName»module = Extension("LinguaFranca«topLevelName»", - sources = ["«topLevelName».c", «FOR src : targetConfig.compileAdditionalSources SEPARATOR ", "» "«src»"«ENDFOR»], - define_macros=[('MODULE_NAME', 'LinguaFranca«topLevelName»')«IF (targetConfig.threads !== 0 || (targetConfig.tracing !== null))», - ('NUMBER_OF_WORKERS', '«targetConfig.threads»')«ENDIF»]) - - setup(name="LinguaFranca«topLevelName»", version="1.0", - ext_modules = [linguafranca«topLevelName»module], - install_requires=['LinguaFrancaBase' «pythonRequiredModules»],) - ''' - - /** - * Generate the necessary Python files. - * @param federate The federate instance - */ - def generatePythonFiles(FederateInstance federate) { - var file = new File(fileConfig.getSrcGenPath.toFile, topLevelName + ".py") - if (file.exists) { - file.delete - } - // Create the necessary directories - if (!file.getParentFile().exists()) { - file.getParentFile().mkdirs(); - } - val codeMaps = #{file.toPath -> CodeMap.fromGeneratedCode(generatePythonCode(federate).toString)} - JavaGeneratorUtils.writeToFile(codeMaps.get(file.toPath).generatedCode, file.toPath) - - val setupPath = fileConfig.getSrcGenPath.resolve("setup.py") - // Handle Python setup - System.out.println("Generating setup file to " + setupPath) - file = setupPath.toFile - if (file.exists) { - // Append - file.delete - } - - // Create the setup file - JavaGeneratorUtils.writeToFile(generatePythonSetupFile, setupPath) - - return codeMaps - } - - /** - * Execute the command that compiles and installs the current Python module - */ - def pythonCompileCode(LFGeneratorContext context) { - // if we found the compile command, we will also find the install command - val installCmd = commandFactory.createCommand( - '''python3''', #["-m", "pip", "install", "--force-reinstall", "."], fileConfig.srcGenPath) - - if (installCmd === null) { - errorReporter.reportError( - "The Python target requires Python >= 3.6, pip >= 20.0.2, and setuptools >= 45.2.0-1 to compile the generated code. " + - "Auto-compiling can be disabled using the \"no-compile: true\" target property.") - return - } - - // Set compile time environment variables - installCmd.setEnvironmentVariable("CC", targetConfig.compiler) // Use gcc as the compiler - installCmd.setEnvironmentVariable("LDFLAGS", targetConfig.linkerFlags) // The linker complains about including pythontarget.h twice (once in the generated code and once in pythontarget.c) - // To avoid this, we force the linker to allow multiple definitions. Duplicate names would still be caught by the - // compiler. - if (installCmd.run(context.cancelIndicator) == 0) { - println("Successfully installed python extension.") - } else { - errorReporter.reportError("Failed to install python extension due to the following errors:\n" + - installCmd.getErrors()) - } - } - - /** - * Generate top-level preambles and #include of pqueue.c and either reactor.c or reactor_threaded.c - * depending on whether threads are specified in target directive. - * As a side effect, this populates the runCommand and compileCommand - * private variables if such commands are specified in the target directive. - */ - override generatePreamble() { - - val models = new LinkedHashSet - - for (r : this.reactors ?: emptyList) { - // The following assumes all reactors have a container. - // This means that generated reactors **have** to be - // added to a resource; not doing so will result in a NPE. - models.add(r.toDefinition.eContainer as Model) - } - // Add the main reactor if it is defined - if (this.mainDef !== null) { - models.add(this.mainDef.reactorClass.toDefinition.eContainer as Model) - } - for (m : models) { - for (p : m.preambles) { - pythonPreamble.append('''«p.code.toText» - ''') - } - } - - code.pr(CGenerator.defineLogLevel(this)) - - if (isFederated) { - // FIXME: Instead of checking - // #ifdef FEDERATED, we could - // use #if (NUMBER_OF_FEDERATES > 1) - // To me, the former is more accurate. - code.pr(''' - #define FEDERATED - ''') - if (targetConfig.coordination === CoordinationType.CENTRALIZED) { - // The coordination is centralized. - code.pr(''' - #define FEDERATED_CENTRALIZED - ''') - } else if (targetConfig.coordination === CoordinationType.DECENTRALIZED) { - // The coordination is decentralized - code.pr(''' - #define FEDERATED_DECENTRALIZED - ''') - } - } - - // Handle target parameters. - // First, if there are federates, then ensure that threading is enabled. - if (isFederated) { - for (federate : federates) { - // The number of threads needs to be at least one larger than the input ports - // to allow the federate to wait on all input ports while allowing an additional - // worker thread to process incoming messages. - if (targetConfig.threads < federate.networkMessageActions.size + 1) { - targetConfig.threads = federate.networkMessageActions.size + 1; - } - } - } - - includeTargetLanguageHeaders() - - code.pr("#include \"core/mixed_radix.h\""); - - code.pr('#define NUMBER_OF_FEDERATES ' + federates.size); - - // Handle target parameters. - // First, if there are federates, then ensure that threading is enabled. - if (targetConfig.threads === 0 && isFederated) { - targetConfig.threads = 1 - } - - super.includeTargetLanguageSourceFiles() - - super.parseTargetParameters() - } - - /** - * Add necessary code to the source and necessary build supports to - * enable the requested serializations in 'enabledSerializations' - */ - override enableSupportForSerializationIfApplicable(CancelIndicator cancelIndicator) { - if (!targetConfig.protoFiles.isNullOrEmpty) { - // Enable support for proto serialization - enabledSerializers.add(SupportedSerializers.PROTO) - } - for (serialization : enabledSerializers) { - switch (serialization) { - case NATIVE: { - val pickler = new FedNativePythonSerialization(); - code.pr(pickler.generatePreambleForSupport.toString); - } - case PROTO: { - // Handle .proto files. - for (name : targetConfig.protoFiles) { - this.processProtoFile(name, cancelIndicator) - val dotIndex = name.lastIndexOf('.') - var rootFilename = name - if (dotIndex > 0) { - rootFilename = name.substring(0, dotIndex) - } - pythonPreamble.append(''' - import «rootFilename»_pb2 as «rootFilename» - ''') - protoNames.add(rootFilename) - } - } - case ROS2: { - // FIXME: Not supported yet - } - } - } - } - - /** - * Process a given .proto file. - * - * Run, if possible, the proto-c protocol buffer code generator to produce - * the required .h and .c files. - * @param filename Name of the file to process. - */ - override processProtoFile(String filename, CancelIndicator cancelIndicator) { - val protoc = commandFactory.createCommand("protoc", - #['''--python_out=«this.fileConfig.getSrcGenPath»''', filename], fileConfig.srcPath) - // val protoc = createCommand("protoc", #['''--python_out=src-gen/«topLevelName»''', topLevelName], codeGenConfig.outPath) - if (protoc === null) { - errorReporter.reportError("Processing .proto files requires libprotoc >= 3.6.1") - return - } - val returnCode = protoc.run(cancelIndicator) - if (returnCode == 0) { - pythonRequiredModules.append(''', 'google-api-python-client' ''') - } else { - errorReporter.reportError("protoc returns error code " + returnCode) - } - } - - /** - * Generate code for the body of a reaction that handles the - * action that is triggered by receiving a message from a remote - * federate. - * @param action The action. - * @param sendingPort The output port providing the data to send. - * @param receivingPort The ID of the destination port. - * @param receivingPortID The ID of the destination port. - * @param sendingFed The sending federate. - * @param receivingFed The destination federate. - * @param receivingBankIndex The receiving federate's bank index, if it is in a bank. - * @param receivingChannelIndex The receiving federate's channel index, if it is a multiport. - * @param type The type. - * @param isPhysical Indicates whether or not the connection is physical - * @param serializer The serializer used on the connection. - */ - override generateNetworkReceiverBody( - Action action, - VarRef sendingPort, - VarRef receivingPort, - int receivingPortID, - FederateInstance sendingFed, - FederateInstance receivingFed, - int receivingBankIndex, - int receivingChannelIndex, - InferredType type, - boolean isPhysical, - SupportedSerializers serializer - ) { - var result = new StringBuilder(); - - // We currently have no way to mark a reaction "unordered" - // in the AST, so we use a magic string at the start of the body. - result.append("// " + ReactionInstance.UNORDERED_REACTION_MARKER + "\n"); - - result.append(''' - // Acquire the GIL (Global Interpreter Lock) to be able to call Python APIs. - PyGILState_STATE gstate; - gstate = PyGILState_Ensure(); - ''') - result.append(PythonGeneratorExtension.generateNetworkReceiverBody( - action, - sendingPort, - receivingPort, - receivingPortID, - sendingFed, - receivingFed, - receivingBankIndex, - receivingChannelIndex, - type, - isPhysical, - serializer, - this - )); - result.append(''' - /* Release the thread. No Python API allowed beyond this point. */ - PyGILState_Release(gstate); - '''); - return result.toString(); - } - - /** - * Generate code for the body of a reaction that handles an output - * that is to be sent over the network. - * @param sendingPort The output port providing the data to send. - * @param receivingPort The variable reference to the destination port. - * @param receivingPortID The ID of the destination port. - * @param sendingFed The sending federate. - * @param sendingBankIndex The bank index of the sending federate, if it is a bank. - * @param sendingChannelIndex The channel index of the sending port, if it is a multiport. - * @param receivingFed The destination federate. - * @param type The type. - * @param isPhysical Indicates whether the connection is physical or not - * @param delay The delay value imposed on the connection using after - * @param serializer The serializer used on the connection. - */ - override generateNetworkSenderBody( - VarRef sendingPort, - VarRef receivingPort, - int receivingPortID, - FederateInstance sendingFed, - int sendingBankIndex, - int sendingChannelIndex, - FederateInstance receivingFed, - InferredType type, - boolean isPhysical, - Delay delay, - SupportedSerializers serializer - ) { - var result = new StringBuilder(); - - // We currently have no way to mark a reaction "unordered" - // in the AST, so we use a magic string at the start of the body. - result.append("// " + ReactionInstance.UNORDERED_REACTION_MARKER + "\n"); - - result.append(''' - // Acquire the GIL (Global Interpreter Lock) to be able to call Python APIs. - PyGILState_STATE gstate; - gstate = PyGILState_Ensure(); - ''') - result.append(PythonGeneratorExtension.generateNetworkSenderBody( - sendingPort, - receivingPort, - receivingPortID, - sendingFed, - sendingBankIndex, - sendingChannelIndex, - receivingFed, - type, - isPhysical, - delay, - serializer, - this - )); - result.append(''' - /* Release the thread. No Python API allowed beyond this point. */ - PyGILState_Release(gstate); - '''); - return result.toString(); - } - - /** - * Create a launcher script that executes all the federates and the RTI. - * - * @param coreFiles The files from the core directory that must be - * copied to the remote machines. - */ - override createFederatedLauncher(ArrayList coreFiles) { - val launcher = new FedPyLauncher( - targetConfig, - fileConfig, - errorReporter - ); - launcher.createLauncher( - coreFiles, - federates, - federationRTIProperties - ); - } - - /** - * Generate the aliases for inputs, outputs, and struct type definitions for - * actions of the specified reactor in the specified federate. - * @param reactor The parsed reactor data structure. - */ - override generateAuxiliaryStructs(ReactorDecl decl) { - val reactor = decl.toDefinition - // First, handle inputs. - for (input : reactor.allInputs) { - if (input.inferredType.isTokenType) { - code.pr(input, ''' - typedef «generic_port_type_with_token» «variableStructType(input, decl)»; - ''') - } else { - code.pr(input, ''' - typedef «generic_port_type» «variableStructType(input, decl)»; - ''') - } - } - // Next, handle outputs. - for (output : reactor.allOutputs) { - if (output.inferredType.isTokenType) { - code.pr(output, ''' - typedef «generic_port_type_with_token» «variableStructType(output, decl)»; - ''') - } else { - code.pr(output, ''' - typedef «generic_port_type» «variableStructType(output, decl)»; - ''') - } - } - // Finally, handle actions. - for (action : reactor.allActions) { - if (currentFederate.contains(action)) { - code.pr(action, ''' - typedef «generic_action_type» «variableStructType(action, decl)»; - ''') - } - - } - } - - /** - * For the specified action, return a declaration for action struct to - * contain the value of the action. - * This will return an empty string for an action with no type. - * @param action The action. - * @return A string providing the value field of the action struct. - */ - override valueDeclaration(Action action) { - return "PyObject* value;" - } - - /** Add necessary include files specific to the target language. - * Note. The core files always need to be (and will be) copied - * uniformly across all target languages. - */ - override includeTargetLanguageHeaders() { - code.pr('''#define _LF_GARBAGE_COLLECTED''') - if (targetConfig.tracing !== null) { - var filename = ""; - if (targetConfig.tracing.traceFileName !== null) { - filename = targetConfig.tracing.traceFileName; - } - code.pr('#define LINGUA_FRANCA_TRACE ' + filename) - } - - code.pr('#include "pythontarget.c"') - if (targetConfig.tracing !== null) { - code.pr('#include "core/trace.c"') - } - } - - /** - * Return true if the host operating system is compatible and - * otherwise report an error and return false. - */ - override isOSCompatible() { - if (JavaGeneratorUtils.isHostWindows) { - if (isFederated) { - errorReporter.reportError( - "Federated LF programs with a Python target are currently not supported on Windows. Exiting code generation." - ) - // Return to avoid compiler errors - return false - } - } - return true; - } - - /** Generate C code from the Lingua Franca model contained by the - * specified resource. This is the main entry point for code - * generation. - * @param resource The resource containing the source code. - * @param context Context relating to invocation of the code generator. - */ - override void doGenerate(Resource resource, LFGeneratorContext context) { - // If there are federates, assign the number of threads in the CGenerator to 1 - if (isFederated) { - targetConfig.threads = 1; - } - - // Prevent the CGenerator from compiling the C code. - // The PythonGenerator will compiler it. - val compileStatus = targetConfig.noCompile; - targetConfig.noCompile = true; - targetConfig.useCmake = false; // Force disable the CMake because - // it interferes with the Python target functionality - val cGeneratedPercentProgress = (IntegratedBuilder.VALIDATED_PERCENT_PROGRESS + 100) / 2 - super.doGenerate(resource, new SubContext( - context, - IntegratedBuilder.VALIDATED_PERCENT_PROGRESS, - cGeneratedPercentProgress - )) - val compilingFederatesContext = new SubContext(context, cGeneratedPercentProgress, 100) - - targetConfig.noCompile = compileStatus - - if (errorsOccurred) { - context.unsuccessfulFinish() - return; - } - - var baseFileName = topLevelName - // Keep a separate file config for each federate - val oldFileConfig = fileConfig; - var federateCount = 0; - val codeMaps = new HashMap - for (federate : federates) { - federateCount++ - if (isFederated) { - topLevelName = baseFileName + '_' + federate.name - fileConfig = new FedFileConfig(fileConfig, federate.name); - } - // Don't generate code if there is no main reactor - if (this.main !== null) { - val codeMapsForFederate = generatePythonFiles(federate) - codeMaps.putAll(codeMapsForFederate) - copyTargetFiles(); - if (!targetConfig.noCompile) { - compilingFederatesContext.reportProgress( - String.format("Validating %d/%d sets of generated files...", federateCount, federates.size()), - 100 * federateCount / federates.size() - ) - // If there are no federates, compile and install the generated code - new PythonValidator(fileConfig, errorReporter, codeMaps, protoNames).doValidate(context) - if (!errorsOccurred() && context.mode != LFGeneratorContext.Mode.LSP_MEDIUM) { - compilingFederatesContext.reportProgress( - String.format("Validation complete. Compiling and installing %d/%d Python modules...", - federateCount, federates.size()), - 100 * federateCount / federates.size() - ) - pythonCompileCode(context) // Why is this invoked here if the current federate is not a parameter? - } - } else { - printSetupInfo(); - } - - if (!isFederated) { - printRunInfo(); - } - } - fileConfig = oldFileConfig; - } - if (isFederated) { - printFedRunInfo(); - } - // Restore filename - topLevelName = baseFileName - if (errorReporter.getErrorsOccurred()) { - context.unsuccessfulFinish() - } else if (!isFederated) { - context.finish(GeneratorResult.Status.COMPILED, '''«topLevelName».py''', fileConfig.srcGenPath, fileConfig, - codeMaps, "python3") - } else { - context.finish(GeneratorResult.Status.COMPILED, fileConfig.name, fileConfig.binPath, fileConfig, codeMaps, - "bash") - } - } - - /** - * Copy Python specific target code to the src-gen directory - */ - def copyTargetFiles() { - // Copy the required target language files into the target file system. - // This will also overwrite previous versions. - FileUtil.copyFileFromClassPath( - "/lib/py/reactor-c-py/include/pythontarget.h", - fileConfig.getSrcGenPath.resolve("pythontarget.h") - ) - FileUtil.copyFileFromClassPath( - "/lib/py/reactor-c-py/lib/pythontarget.c", - fileConfig.getSrcGenPath.resolve("pythontarget.c") - ) - FileUtil.copyFileFromClassPath( - "/lib/c/reactor-c/include/ctarget.h", - fileConfig.getSrcGenPath.resolve("ctarget.h") - ) - } - - /** Return the function name in Python - * @param reactor The reactor - * @param reactionIndex The reaction index. - * @return The function name for the reaction. - */ - def pythonReactionFunctionName(int reactionIndex) { - "reaction_function_" + reactionIndex - } - - /** - * Generate code for the body of a reaction that takes an input and - * schedules an action with the value of that input. - * @param action The action to schedule - * @param port The port to read from - */ - override generateDelayBody(Action action, VarRef port) { - val ref = JavaAstUtils.generateVarRef(port); - // Note that the action.type set by the base class is actually - // the port type. - if (action.inferredType.isTokenType) { - ''' - if («ref»->is_present) { - // Put the whole token on the event queue, not just the payload. - // This way, the length and element_size are transported. - schedule_token(«action.name», 0, «ref»->token); - } - ''' - } else { - ''' - // Create a token. - #if NUMBER_OF_WORKERS > 0 - // Need to lock the mutex first. - lf_mutex_lock(&mutex); - #endif - lf_token_t* t = create_token(sizeof(PyObject*)); - #if NUMBER_OF_WORKERS > 0 - lf_mutex_unlock(&mutex); - #endif - t->value = self->_lf_«ref»->value; - t->length = 1; // Length is 1 - - // Pass the token along - schedule_token(«action.name», 0, t); - ''' - } - } - - /** - * Generate code for the body of a reaction that is triggered by the - * given action and writes its value to the given port. This realizes - * the receiving end of a logical delay specified with the 'after' - * keyword. - * @param action The action that triggers the reaction - * @param port The port to write to. - */ - override generateForwardBody(Action action, VarRef port) { - val outputName = JavaAstUtils.generateVarRef(port) - if (action.inferredType.isTokenType) { - super.generateForwardBody(action, port) - } else { - ''' - SET(«outputName», «action.name»->token->value); - ''' - } - } - - - /** - * Generate necessary Python-specific initialization code for reaction that belongs to reactor - * decl. - * - * @param reaction The reaction to generate Python-specific initialization for. - * @param decl The reactor to which reaction belongs to. - * @param pyObjectDescriptor For each port object created, a Python-specific descriptor will be added to this that - * then can be used as an argument to Py_BuildValue - * (@see docs.python.org/3/c-api). - * @param pyObjects A "," delimited list of expressions that would be (or result in a creation of) a PyObject. - */ - protected def void generatePythonInitializationForReaction( - Reaction reaction, - ReactorDecl decl, - StringBuilder pyObjectDescriptor, - StringBuilder pyObjects - ) { - var actionsAsTriggers = new LinkedHashSet(); - val Reactor reactor = decl.toDefinition; - - // Next, add the triggers (input and actions; timers are not needed). - // TODO: handle triggers - for (TriggerRef trigger : reaction.triggers ?: emptyList) { - if (trigger instanceof VarRef) { - if (trigger.variable instanceof Port) { - generatePortVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, trigger, decl) - } else if (trigger.variable instanceof Action) { - actionsAsTriggers.add(trigger.variable as Action) - generateActionVariableToSendToPythonReaction(pyObjectDescriptor, pyObjects, - trigger.variable as Action, decl) - } - } - } - if (reaction.triggers === null || reaction.triggers.size === 0) { - // No triggers are given, which means react to any input. - // Declare an argument for every input. - // NOTE: this does not include contained outputs. - for (input : reactor.inputs) { - generateInputVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, input, decl) - } - } - - // Next add non-triggering inputs. - for (VarRef src : reaction.sources ?: emptyList) { - if (src.variable instanceof Port) { - generatePortVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, src, decl) - } else if (src.variable instanceof Action) { - // TODO: handle actions - actionsAsTriggers.add(src.variable as Action) - generateActionVariableToSendToPythonReaction(pyObjectDescriptor, pyObjects, src.variable as Action, - decl) - } - } - - // Next, handle effects - if (reaction.effects !== null) { - for (effect : reaction.effects) { - if (effect.variable instanceof Action) { - // It is an action, not an output. - // If it has already appeared as trigger, do not redefine it. - if (!actionsAsTriggers.contains(effect.variable)) { - generateActionVariableToSendToPythonReaction(pyObjectDescriptor, pyObjects, - effect.variable as Action, decl) - } - } else { - if (effect.variable instanceof Output) { - generateOutputVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, - effect.variable as Output, decl) - } else if (effect.variable instanceof Input) { - // It is the input of a contained reactor. - generateVariablesForSendingToContainedReactors(pyObjectDescriptor, pyObjects, effect.container, - effect.variable as Input, decl) - } else { - errorReporter.reportError( - reaction, - "In generateReaction(): " + effect.variable.name + " is neither an input nor an output." - ) - } - - } - } - } - } - - /** Generate a reaction function definition for a reactor. - * This function has a single argument that is a void* pointing to - * a struct that contains parameters, state variables, inputs (triggering or not), - * actions (triggering or produced), and outputs. - * @param reaction The reaction. - * @param reactor The reactor. - * @param reactionIndex The position of the reaction within the reactor. - */ - override generateReaction(Reaction reaction, ReactorDecl decl, int reactionIndex) { - - val reactor = decl.toDefinition - - // Delay reactors and top-level reactions used in the top-level reactor(s) in federated execution are generated in C - if (reactor.name.contains(GEN_DELAY_CLASS_NAME) || - ((decl === this.mainDef?.reactorClass) && reactor.isFederated)) { - super.generateReaction(reaction, decl, reactionIndex) - return - } - - // Contains "O" characters. The number of these characters depend on the number of inputs to the reaction - val StringBuilder pyObjectDescriptor = new StringBuilder() - - // Contains the actual comma separated list of inputs to the reaction of type generic_port_instance_struct or generic_port_instance_with_token_struct. - // Each input must be cast to (PyObject *) - val StringBuilder pyObjects = new StringBuilder() - - // Create a unique function name for each reaction. - val functionName = reactionFunctionName(decl, reactionIndex) - - // Generate the function name in Python - val pythonFunctionName = pythonReactionFunctionName(reactionIndex); - - code.pr('void ' + functionName + '(void* instance_args) {') - code.indent() - - // First, generate C initializations - super.generateInitializationForReaction("", reaction, decl, reactionIndex) - - code.prSourceLineNumber(reaction.code) - - // Ensure that GIL is locked - code.pr(''' - // Acquire the GIL (Global Interpreter Lock) to be able to call Python APIs. - PyGILState_STATE gstate; - gstate = PyGILState_Ensure(); - ''') - - // Generate Python-related initializations - generatePythonInitializationForReaction(reaction, decl, pyObjectDescriptor, pyObjects) - - // Call the Python reaction - code.pr(''' - - DEBUG_PRINT("Calling reaction function «decl.name».«pythonFunctionName»"); - PyObject *rValue = PyObject_CallObject( - self->_lf_py_reaction_function_«reactionIndex», - Py_BuildValue("(«pyObjectDescriptor»)" «pyObjects») - ); - if (rValue == NULL) { - error_print("FATAL: Calling reaction «decl.name».«pythonFunctionName» failed."); - if (PyErr_Occurred()) { - PyErr_PrintEx(0); - PyErr_Clear(); // this will reset the error indicator so we can run Python code again - } - /* Release the thread. No Python API allowed beyond this point. */ - PyGILState_Release(gstate); - Py_FinalizeEx(); - exit(1); - } - - /* Release the thread. No Python API allowed beyond this point. */ - PyGILState_Release(gstate); - ''') - - code.unindent() - code.pr("}") - - // Now generate code for the deadline violation function, if there is one. - if (reaction.deadline !== null) { - // The following name has to match the choice in generateReactionInstances - val deadlineFunctionName = decl.name.toLowerCase + '_deadline_function' + reactionIndex - - code.pr('void ' + deadlineFunctionName + '(void* instance_args) {') - code.indent(); - - super.generateInitializationForReaction("", reaction, decl, reactionIndex) - - code.pr(''' - // Acquire the GIL (Global Interpreter Lock) to be able to call Python APIs. - PyGILState_STATE gstate; - gstate = PyGILState_Ensure(); - - DEBUG_PRINT("Calling deadline function «decl.name».«deadlineFunctionName»"); - PyObject *rValue = PyObject_CallObject( - self->_lf_py_deadline_function_«reactionIndex», - Py_BuildValue("(«pyObjectDescriptor»)" «pyObjects») - ); - if (rValue == NULL) { - error_print("FATAL: Calling reaction «decl.name».«deadlineFunctionName» failed.\n"); - if (rValue == NULL) { - if (PyErr_Occurred()) { - PyErr_PrintEx(0); - PyErr_Clear(); // this will reset the error indicator so we can run Python code again - } - } - /* Release the thread. No Python API allowed beyond this point. */ - PyGILState_Release(gstate); - Py_FinalizeEx(); - exit(1); - } - - /* Release the thread. No Python API allowed beyond this point. */ - PyGILState_Release(gstate); - ''') - - code.unindent() - code.pr("}") - } - } - - /** - * Generate code for parameter variables of a reactor in the form "parameter.type parameter.name;" - * - * FIXME: for now we assume all parameters are int. This is to circumvent the issue of parameterized - * port widths for now. - * - * @param reactor The reactor. - * @param builder The place that the generated code is written to. - * @return - */ - override generateParametersForReactor(CodeBuilder builder, Reactor reactor) { - for (parameter : reactor.allParameters) { - builder.prSourceLineNumber(parameter) - // Assume all parameters are integers - builder.pr('''int «parameter.name» ;'''); - } - } - - /** - * Generate code that initializes the state variables for a given instance. - * Unlike parameters, state variables are uniformly initialized for all instances - * of the same reactor. This task is left to Python code to allow for more liberal - * state variable assignments. - * @param instance The reactor class instance - * @return Initialization code fore state variables of instance - */ - override generateStateVariableInitializations(ReactorInstance instance) { - // Do nothing - } - - /** - * Generate runtime initialization code in C for parameters of a given reactor instance. - * All parameters are also initialized in Python code, but those parameters that are - * used as width must be also initialized in C. - * - * FIXME: Here, we use a hack: we attempt to convert the parameter initialization to an integer. - * If it succeeds, we proceed with the C initialization. If it fails, we defer initialization - * to Python. - * - * Generate runtime initialization code for parameters of a given reactor instance - * @param instance The reactor instance. - */ - override void generateParameterInitialization(ReactorInstance instance) { - // Mostly ignore the initialization in C - // The actual initialization will be done in Python - // Except if the parameter is a width (an integer) - // Here, we attempt to convert the parameter value to - // integer. If it succeeds, we also initialize it in C. - // If it fails, we defer the initialization to Python. - var nameOfSelfStruct = CUtil.reactorRef(instance) - for (parameter : instance.parameters) { - val initializer = parameter.getInitializer - try { - // Attempt to convert it to integer - val number = Integer.parseInt(initializer); - initializeTriggerObjects.pr(''' - «nameOfSelfStruct»->«parameter.name» = «number»; - ''') - } catch (NumberFormatException ex) { - // Ignore initialization in C for this parameter - } - } - } - - /** - * This function is overridden in the Python generator to do nothing. - * The state variables are initialized in Python code directly. - * @param reactor The reactor. - * @param builder The place that the generated code is written to. - * @return - */ - override generateStateVariablesForReactor(CodeBuilder builder, Reactor reactor) { - // Do nothing - } - - /** - * Generates C preambles defined by user for a given reactor - * Since the Python generator expects preambles written in C, - * this function is overridden and does nothing. - * @param reactor The given reactor - */ - override generateUserPreamblesForReactor(Reactor reactor) { - // Do nothing - } - - /** - * Generate code that is executed while the reactor instance is being initialized. - * This wraps the reaction functions in a Python function. - * @param instance The reactor instance. - * @param reactions The reactions of this instance. - */ - override void generateReactorInstanceExtension(ReactorInstance instance) { - var nameOfSelfStruct = CUtil.reactorRef(instance) - var reactor = instance.definition.reactorClass.toDefinition - - // Delay reactors and top-level reactions used in the top-level reactor(s) in federated execution are generated in C - if (reactor.name.contains(GEN_DELAY_CLASS_NAME) || - ((instance.definition.reactorClass === this.mainDef?.reactorClass) && reactor.isFederated)) { - return - } - - // Initialize the name field to the unique name of the instance - initializeTriggerObjects.pr(''' - «nameOfSelfStruct»->_lf_name = "«instance.uniqueID»_lf"; - '''); - - for (reaction : instance.reactions) { - if (currentFederate.contains(reaction.getDefinition())) { - val pythonFunctionName = pythonReactionFunctionName(reaction.index) - // Create a PyObject for each reaction - initializeTriggerObjects.pr(''' - «nameOfSelfStruct»->_lf_py_reaction_function_«reaction.index» = - get_python_function("__main__", - «nameOfSelfStruct»->_lf_name, - «CUtil.runtimeIndex(instance)», - "«pythonFunctionName»"); - ''') - - if (reaction.definition.deadline !== null) { - initializeTriggerObjects.pr(''' - «nameOfSelfStruct»->_lf_py_deadline_function_«reaction.index» = - get_python_function("«topLevelName»", - «nameOfSelfStruct»->_lf_name, - «CUtil.runtimeIndex(instance)», - "deadline_function_«reaction.index»"); - ''') - } - } - } - } - - /** - * This function is provided to allow extensions of the CGenerator to append the structure of the self struct - * @param selfStructBody The body of the self struct - * @param decl The reactor declaration for the self struct - * @param constructorCode Code that is executed when the reactor is instantiated - */ - override generateSelfStructExtension( - CodeBuilder selfStructBody, - ReactorDecl decl, - CodeBuilder constructorCode - ) { - val reactor = decl.toDefinition - // Add the name field - selfStructBody.pr('''char *_lf_name; - '''); - - var reactionIndex = 0 - for (reaction : reactor.allReactions) { - // Create a PyObject for each reaction - selfStructBody.pr('''PyObject* _lf_py_reaction_function_«reactionIndex»;''') - - if (reaction.deadline !== null) { - selfStructBody.pr('''PyObject* _lf_py_deadline_function_«reactionIndex»;''') - } - - reactionIndex++ - } - } - - /** - * Generate code to convert C actions to Python action capsules - * @see pythontarget.h. - * @param pyObjectDescriptor A string representing a list of Python format types (e.g., "O") that - * can be passed to Py_BuildValue. The object type for the converted action will - * be appended to this string (e.g., "OO"). - * @param pyObjects A string containing a list of comma-separated expressions that will create the - * action capsules. - * @param action The action itself. - * @param decl The reactor decl that contains the action. - */ - def generateActionVariableToSendToPythonReaction(StringBuilder pyObjectDescriptor, StringBuilder pyObjects, - Action action, ReactorDecl decl) { - pyObjectDescriptor.append("O") - // Values passed to an action are always stored in the token->value. - // However, sometimes token might not be initialized. Therefore, this function has an internal check for NULL in case token is not initialized. - pyObjects.append(''', convert_C_action_to_py(«action.name»)''') - } - - /** - * Generate code to convert C ports to Python ports capsules (@see pythontarget.h). - * - * The port may be an input of the reactor or an output of a contained reactor. - * - * @param pyObjectDescriptor A string representing a list of Python format types (e.g., "O") that - * can be passed to Py_BuildValue. The object type for the converted port will - * be appended to this string (e.g., "OO"). - * @param pyObjects A string containing a list of comma-separated expressions that will create the - * port capsules. - * @param port The port itself. - * @param decl The reactor decl that contains the port. - */ - private def generatePortVariablesToSendToPythonReaction( - StringBuilder pyObjectDescriptor, - StringBuilder pyObjects, - VarRef port, - ReactorDecl decl - ) { - if (port.variable instanceof Input) { - generateInputVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, port.variable as Input, decl) - } else { - pyObjectDescriptor.append("O") - val output = port.variable as Output - val reactorName = port.container.name - // port is an output of a contained reactor. - if (port.container.widthSpec !== null) { - var String widthSpec = "-2" - if (JavaAstUtils.isMultiport(port.variable as Port)) { - widthSpec = '''self->_lf_«reactorName»[i].«output.name»_width''' - } - // Output is in a bank. - // Create a Python list - generatePythonListForContainedBank(reactorName, output, widthSpec) - pyObjects.append(''', «reactorName»_py_list''') - } else { - var String widthSpec = "-2" - if (JavaAstUtils.isMultiport(port.variable as Port)) { - widthSpec = '''«port.container.name».«port.variable.name»_width''' - } - pyObjects.append(''', convert_C_port_to_py(«reactorName».«port.variable.name», «widthSpec»)''') - } - } - } - - /** - * Generate code that creates a Python list (i.e., []) for contained banks to be passed to Python reactions. - * The Python reaction will then subsequently be able to address each individual bank member of the contained - * bank using an index or an iterator. Each list member will contain the given port - * (which could be a multiport with a width determined by widthSpec). - * - * This is to accommodate reactions like reaction() -> s.out where s is a bank. In this example, - * the generated Python function will have the signature reaction_function_0(self, s_out), where - * s_out is a list of out ports. This will later be turned into the proper s.out format using the - * Python code generated in {@link #generatePythonPortVariableInReaction}. - * - * @param reactorName The name of the bank of reactors (which is the name of the reactor class). - * @param port The port that should be put in the Python list. - * @param widthSpec A string that should be -2 for non-multiports and the width expression for multiports. - */ - protected def void generatePythonListForContainedBank(String reactorName, Port port, String widthSpec) { - code.pr(''' - PyObject* «reactorName»_py_list = PyList_New(«reactorName»_width); - - if(«reactorName»_py_list == NULL) { - error_print("Could not create the list needed for «reactorName»."); - if (PyErr_Occurred()) { - PyErr_PrintEx(0); - PyErr_Clear(); // this will reset the error indicator so we can run Python code again - } - /* Release the thread. No Python API allowed beyond this point. */ - PyGILState_Release(gstate); - Py_FinalizeEx(); - exit(1); - } - - for (int i = 0; i < «reactorName»_width; i++) { - if (PyList_SetItem( - «reactorName»_py_list, - i, - convert_C_port_to_py( - self->_lf_«reactorName»[i].«port.name», - «widthSpec» - ) - ) != 0) { - error_print("Could not add elements to the list for «reactorName»."); - if (PyErr_Occurred()) { - PyErr_PrintEx(0); - PyErr_Clear(); // this will reset the error indicator so we can run Python code again - } - /* Release the thread. No Python API allowed beyond this point. */ - PyGILState_Release(gstate); - Py_FinalizeEx(); - exit(1); - } - } - - ''') - } - - /** Generate into the specified string builder the code to - * send local variables for output ports to a Python reaction function - * from the "self" struct. - * @param builder The string builder into which to write the code. - * @param structs A map from reactor instantiations to a place to write - * struct fields. - * @param output The output port. - * @param decl The reactor declaration. - */ - private def generateOutputVariablesToSendToPythonReaction( - StringBuilder pyObjectDescriptor, - StringBuilder pyObjects, - Output output, - ReactorDecl decl - ) { - // Unfortunately, for the SET macros to work out-of-the-box for - // multiports, we need an array of *pointers* to the output structs, - // but what we have on the self struct is an array of output structs. - // So we have to handle multiports specially here a construct that - // array of pointers. - // FIXME: The C Generator also has this awkwardness. It makes the code generators - // unnecessarily difficult to maintain, and it may have performance consequences as well. - // Maybe we should change the SET macros. - if (!JavaAstUtils.isMultiport(output)) { - pyObjectDescriptor.append("O") - pyObjects.append(''', convert_C_port_to_py(«output.name», -2)''') - } else { - // Set the _width variable. - pyObjectDescriptor.append("O") - pyObjects.append(''', convert_C_port_to_py(«output.name»,«output.name»_width) ''') - } - } - - /** Generate into the specified string builder the code to - * pass local variables for sending data to an input - * of a contained reaction (e.g. for a deadline violation). - * @param builder The string builder. - * @param definition AST node defining the reactor within which this occurs - * @param input Input of the contained reactor. - */ - private def generateVariablesForSendingToContainedReactors( - StringBuilder pyObjectDescriptor, - StringBuilder pyObjects, - Instantiation definition, - Input input, - ReactorDecl decl - ) { - pyObjectDescriptor.append("O") - - if (definition.widthSpec !== null) { - var String widthSpec = "-2" - if (JavaAstUtils.isMultiport(input)) { - widthSpec = '''self->_lf_«definition.name»[i].«input.name»_width''' - } - // Contained reactor is a bank. - // Create a Python list - generatePythonListForContainedBank(definition.name, input, widthSpec); - pyObjects.append(''', «definition.name»_py_list''') - } - else { - var String widthSpec = "-2" - if (JavaAstUtils.isMultiport(input)) { - widthSpec = '''«definition.name».«input.name»_width''' - } - pyObjects. - append(''', convert_C_port_to_py(«definition.name».«input.name», «widthSpec»)''') - } - } - - /** Generate into the specified string builder the code to - * send local variables for input ports to a Python reaction function - * from the "self" struct. - * @param builder The string builder into which to write the code. - * @param structs A map from reactor instantiations to a place to write - * struct fields. - * @param input The input port. - * @param reactor The reactor. - */ - private def generateInputVariablesToSendToPythonReaction( - StringBuilder pyObjectDescriptor, - StringBuilder pyObjects, - Input input, - ReactorDecl decl - ) { - // Create the local variable whose name matches the input name. - // If the input has not been declared mutable, then this is a pointer - // to the upstream output. Otherwise, it is a copy of the upstream output, - // which nevertheless points to the same token and value (hence, as done - // below, we have to use writable_copy()). There are 8 cases, - // depending on whether the input is mutable, whether it is a multiport, - // and whether it is a token type. - // Easy case first. - if (!input.isMutable && !JavaAstUtils.isMultiport(input)) { - // Non-mutable, non-multiport, primitive type. - pyObjectDescriptor.append("O") - pyObjects.append(''', convert_C_port_to_py(«input.name», «input.name»_width)''') - } else if (input.isMutable && !JavaAstUtils.isMultiport(input)) { - // Mutable, non-multiport, primitive type. - // TODO: handle mutable - pyObjectDescriptor.append("O") - pyObjects.append(''', convert_C_port_to_py(«input.name», «input.name»_width)''') - } else if (!input.isMutable && JavaAstUtils.isMultiport(input)) { - // Non-mutable, multiport, primitive. - // TODO: support multiports - pyObjectDescriptor.append("O") - pyObjects.append(''', convert_C_port_to_py(«input.name»,«input.name»_width) ''') - } else { - // Mutable, multiport, primitive type - // TODO: support mutable multiports - pyObjectDescriptor.append("O") - pyObjects.append(''', convert_C_port_to_py(«input.name»,«input.name»_width) ''') - } - } - - /** - * Write a Dockerfile for the current federate as given by filename. - * The file will go into src-gen/filename.Dockerfile. - * If there is no main reactor, then no Dockerfile will be generated - * (it wouldn't be very useful). - * @param The directory where the docker compose file is generated. - * @param The name of the docker file. - * @param The name of the federate. - */ - override writeDockerFile(File dockerComposeDir, String dockerFileName, String federateName) { - var srcGenPath = fileConfig.getSrcGenPath - val dockerFile = srcGenPath + File.separator + dockerFileName - // If a dockerfile exists, remove it. - var file = new File(dockerFile) - if (file.exists) { - file.delete - } - - if (this.mainDef === null) { - return - } - - val OS = System.getProperty("os.name").toLowerCase(); - var dockerComposeCommand = (OS.indexOf("nux") >= 0) ? "docker-compose" : "docker compose" - - val contents = new CodeBuilder() - contents.pr(''' - # Generated docker file for «topLevelName».lf in «srcGenPath». - # For instructions, see: https://github.com/icyphy/lingua-franca/wiki/Containerized-Execution - FROM python:slim - WORKDIR /lingua-franca/«topLevelName» - RUN set -ex && apt-get update && apt-get install -y python3-pip - COPY . src-gen - RUN cd src-gen && python3 setup.py install && cd .. - ENTRYPOINT ["python3", "src-gen/«topLevelName».py"] - ''') - contents.writeToFile(dockerFile) - println('''Dockerfile for «topLevelName» written to ''' + dockerFile) - println(''' - ##################################### - To build the docker image, go to «dockerComposeDir» and run: - - «dockerComposeCommand» build «federateName» - - ##################################### - ''') - } - - /** - * Convert C types to formats used in Py_BuildValue and PyArg_PurseTuple. - * This is unused but will be useful to enable inter-compatibility between - * C and Python reactors. - * @param type C type - */ - def pyBuildValueArgumentType(String type) { - switch (type) { - case "int": "i" - case "string": "s" - case "char": "b" - case "short int": "h" - case "long": "l" - case "unsigned char": "B" - case "unsigned short int": "H" - case "unsigned int": "I" - case "unsigned long": "k" - case "long long": "L" - case "interval_t": "L" - case "unsigned long long": "K" - case "double": "d" - case "float": "f" - case "Py_complex": "D" - case "Py_complex*": "D" - case "Py_Object": "O" - case "Py_Object*": "O" - default: "O" - } - } -} From ce4b600948a4ce610ca680f9e66438d69593a243 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Mon, 28 Feb 2022 09:04:30 +0100 Subject: [PATCH 28/30] move methods for file writing to FileUtil --- .../lflang/generator/JavaGeneratorUtils.java | 37 --------------- .../org/lflang/generator/cpp/CppGenerator.kt | 46 +++++++++++++++---- .../generator/python/PythonGenerator.java | 4 +- .../org/lflang/generator/ts/TSGenerator.kt | 9 ++-- org.lflang/src/org/lflang/util/FileUtil.java | 35 ++++++++++++++ 5 files changed, 79 insertions(+), 52 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java index 585bbc4924..eb8bd28a11 100644 --- a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java +++ b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java @@ -1,11 +1,9 @@ package org.lflang.generator; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -319,41 +317,6 @@ public static LFResource getLFResource( } /** - * Write text to a file. - * @param text The text to be written. - * @param path The file to write the code to. - * @param skipIfUnchanged If true, don't overwrite the destination file if its content would not be changed - */ - public static void writeToFile(String text, Path path, boolean skipIfUnchanged) throws IOException { - Files.createDirectories(path.getParent()); - final byte[] bytes = text.getBytes(); - if (skipIfUnchanged && Files.isRegularFile(path)) { - if (Arrays.equals(bytes, Files.readAllBytes(path))) { - return; - } - } - Files.write(path, text.getBytes()); - } - - /** - * Write text to a file. - * @param text The text to be written. - * @param path The file to write the code to. - */ - public static void writeToFile(String text, Path path) throws IOException { - writeToFile(text, path, false); - } - - /** - * Write text to a file. - * @param text The text to be written. - * @param path The file to write the code to. - */ - public static void writeToFile(CharSequence text, Path path) throws IOException { - writeToFile(text.toString(), path, false); - } - - /** * If the mode is Mode.EPOCH (the code generator is running in an * Eclipse IDE), then refresh the project. This will ensure that * any generated files become visible in the project. diff --git a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt index b9ee417a3d..b446c5d6c9 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt @@ -28,7 +28,6 @@ package org.lflang.generator.cpp import org.eclipse.emf.ecore.resource.Resource import org.lflang.ErrorReporter -import org.lflang.FileConfig import org.lflang.Target import org.lflang.generator.LFGeneratorContext.Mode import org.lflang.TargetProperty @@ -38,7 +37,6 @@ import org.lflang.generator.CodeMap import org.lflang.generator.GeneratorBase import org.lflang.generator.GeneratorResult import org.lflang.generator.IntegratedBuilder -import org.lflang.generator.JavaGeneratorUtils import org.lflang.generator.LFGeneratorContext import org.lflang.generator.TargetTypes import org.lflang.generator.canGenerate @@ -167,7 +165,7 @@ class CppGenerator( val mainCodeMap = CodeMap.fromGeneratedCode(CppMainGenerator(mainReactor, targetConfig, cppFileConfig).generateCode()) cppSources.add(mainFile) codeMaps[srcGenPath.resolve(mainFile)] = mainCodeMap - JavaGeneratorUtils.writeToFile(mainCodeMap.generatedCode, srcGenPath.resolve(mainFile), true) + FileUtil.writeToFile(mainCodeMap.generatedCode, srcGenPath.resolve(mainFile), true) // generate header and source files for all reactors for (r in reactors) { @@ -181,8 +179,16 @@ class CppGenerator( val headerCodeMap = CodeMap.fromGeneratedCode(generator.generateHeader()) codeMaps[srcGenPath.resolve(headerFile)] = headerCodeMap - JavaGeneratorUtils.writeToFile(headerCodeMap.generatedCode, srcGenPath.resolve(headerFile), true) - JavaGeneratorUtils.writeToFile(reactorCodeMap.generatedCode, srcGenPath.resolve(sourceFile), true) + FileUtil.writeToFile( + headerCodeMap.generatedCode, + srcGenPath.resolve(headerFile), + true + ) + FileUtil.writeToFile( + reactorCodeMap.generatedCode, + srcGenPath.resolve(sourceFile), + true + ) } // generate file level preambles for all resources @@ -196,19 +202,39 @@ class CppGenerator( val headerCodeMap = CodeMap.fromGeneratedCode(generator.generateHeader()) codeMaps[srcGenPath.resolve(headerFile)] = headerCodeMap - JavaGeneratorUtils.writeToFile(headerCodeMap.generatedCode, srcGenPath.resolve(headerFile), true) - JavaGeneratorUtils.writeToFile(preambleCodeMap.generatedCode, srcGenPath.resolve(sourceFile), true) + FileUtil.writeToFile( + headerCodeMap.generatedCode, + srcGenPath.resolve(headerFile), + true + ) + FileUtil.writeToFile( + preambleCodeMap.generatedCode, + srcGenPath.resolve(sourceFile), + true + ) } // generate the cmake scripts val cmakeGenerator = CppCmakeGenerator(targetConfig, cppFileConfig) val srcGenRoot = fileConfig.srcGenBasePath val pkgName = fileConfig.srcGenPkgPath.fileName.toString() - JavaGeneratorUtils.writeToFile(cmakeGenerator.generateRootCmake(pkgName), srcGenRoot.resolve("CMakeLists.txt"), true) - JavaGeneratorUtils.writeToFile(cmakeGenerator.generateCmake(cppSources), srcGenPath.resolve("CMakeLists.txt"), true) + FileUtil.writeToFile( + cmakeGenerator.generateRootCmake(pkgName), + srcGenRoot.resolve("CMakeLists.txt"), + true + ) + FileUtil.writeToFile( + cmakeGenerator.generateCmake(cppSources), + srcGenPath.resolve("CMakeLists.txt"), + true + ) var subdir = srcGenPath.parent while (subdir != srcGenRoot) { - JavaGeneratorUtils.writeToFile(cmakeGenerator.generateSubdirCmake(), subdir.resolve("CMakeLists.txt"), true) + FileUtil.writeToFile( + cmakeGenerator.generateSubdirCmake(), + subdir.resolve("CMakeLists.txt"), + true + ) subdir = subdir.parent } diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java index b21e2f9d45..9a29f27fa9 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java @@ -300,7 +300,7 @@ public Map generatePythonFiles(FederateInstance federate) throws } Map codeMaps = new HashMap<>(); codeMaps.put(filePath, CodeMap.fromGeneratedCode(generatePythonCode(federate).toString())); - JavaGeneratorUtils.writeToFile(codeMaps.get(filePath).getGeneratedCode(), filePath); + FileUtil.writeToFile(codeMaps.get(filePath).getGeneratedCode(), filePath); Path setupPath = fileConfig.getSrcGenPath().resolve("setup.py"); // Handle Python setup @@ -308,7 +308,7 @@ public Map generatePythonFiles(FederateInstance federate) throws Files.deleteIfExists(setupPath); // Create the setup file - JavaGeneratorUtils.writeToFile(generatePythonSetupFile(), setupPath); + FileUtil.writeToFile(generatePythonSetupFile(), setupPath); return codeMaps; } diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index 165e9e5a35..244cac7bec 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -270,7 +270,7 @@ class TSGenerator( tsCode.append(reactorGenerator.generateReactorInstanceAndStart(this.mainDef, mainParameters)) val codeMap = CodeMap.fromGeneratedCode(tsCode.toString()) codeMaps[tsFilePath] = codeMap - JavaGeneratorUtils.writeToFile(codeMap.generatedCode, tsFilePath) + FileUtil.writeToFile(codeMap.generatedCode, tsFilePath) if (targetConfig.dockerOptions != null && isFederated) { println("WARNING: Federated Docker file generation is not supported on the Typescript target. No docker file is generated.") @@ -279,8 +279,11 @@ class TSGenerator( val dockerComposeFile = fileConfig.srcGenPath.resolve("docker-compose.yml") val dockerGenerator = TSDockerGenerator(tsFileName) println("docker file written to $dockerFilePath") - JavaGeneratorUtils.writeToFile(dockerGenerator.generateDockerFileContent(), dockerFilePath) - JavaGeneratorUtils.writeToFile(dockerGenerator.generateDockerComposeFileContent(), dockerComposeFile) + FileUtil.writeToFile(dockerGenerator.generateDockerFileContent(), dockerFilePath) + FileUtil.writeToFile( + dockerGenerator.generateDockerComposeFileContent(), + dockerComposeFile + ) } } diff --git a/org.lflang/src/org/lflang/util/FileUtil.java b/org.lflang/src/org/lflang/util/FileUtil.java index 76a6bac500..3cb897fe1e 100644 --- a/org.lflang/src/org/lflang/util/FileUtil.java +++ b/org.lflang/src/org/lflang/util/FileUtil.java @@ -386,4 +386,39 @@ public static IResource getIResource(java.net.URI uri) { } return resource; } + + /** + * Write text to a file. + * @param text The text to be written. + * @param path The file to write the code to. + * @param skipIfUnchanged If true, don't overwrite the destination file if its content would not be changed + */ + public static void writeToFile(String text, Path path, boolean skipIfUnchanged) throws IOException { + Files.createDirectories(path.getParent()); + final byte[] bytes = text.getBytes(); + if (skipIfUnchanged && Files.isRegularFile(path)) { + if (Arrays.equals(bytes, Files.readAllBytes(path))) { + return; + } + } + Files.write(path, text.getBytes()); + } + + /** + * Write text to a file. + * @param text The text to be written. + * @param path The file to write the code to. + */ + public static void writeToFile(String text, Path path) throws IOException { + writeToFile(text, path, false); + } + + /** + * Write text to a file. + * @param text The text to be written. + * @param path The file to write the code to. + */ + public static void writeToFile(CharSequence text, Path path) throws IOException { + writeToFile(text.toString(), path, false); + } } From a7e8f5057c86060bf28217ea4a56e6d5849a8ed5 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Mon, 28 Feb 2022 09:10:48 +0100 Subject: [PATCH 29/30] rename JavaGeneratorUtils -> GeneratorUtils --- .../src/org/lflang/generator/GeneratorBase.xtend | 12 ++++++------ ...{JavaGeneratorUtils.java => GeneratorUtils.java} | 6 +++--- .../org/lflang/generator/LFGeneratorContext.java | 2 +- .../src/org/lflang/generator/c/CCmakeCompiler.java | 4 ++-- .../src/org/lflang/generator/c/CGenerator.xtend | 10 +++++----- .../lflang/generator/python/PythonGenerator.java | 4 ++-- .../src/org/lflang/generator/ts/TSGenerator.kt | 13 +++++++------ 7 files changed, 26 insertions(+), 25 deletions(-) rename org.lflang/src/org/lflang/generator/{JavaGeneratorUtils.java => GeneratorUtils.java} (99%) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 93c6c350dc..85f97399c2 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -283,8 +283,8 @@ abstract class GeneratorBase extends AbstractLFValidator { */ def void doGenerate(Resource resource, LFGeneratorContext context) { - JavaGeneratorUtils.setTargetConfig( - context, JavaGeneratorUtils.findTarget(fileConfig.resource), targetConfig, errorReporter + GeneratorUtils.setTargetConfig( + context, GeneratorUtils.findTarget(fileConfig.resource), targetConfig, errorReporter ) cleanIfNeeded(context) @@ -327,14 +327,14 @@ abstract class GeneratorBase extends AbstractLFValidator { // to validate, which happens in setResources(). setReactorsAndInstantiationGraph(context.mode) - JavaGeneratorUtils.validate(context, fileConfig, instantiationGraph, errorReporter) - val allResources = JavaGeneratorUtils.getResources(reactors) + GeneratorUtils.validate(context, fileConfig, instantiationGraph, errorReporter) + val allResources = GeneratorUtils.getResources(reactors) resources.addAll(allResources.stream() // FIXME: This filter reproduces the behavior of the method it replaces. But why must it be so complicated? Why are we worried about weird corner cases like this? .filter [it | it != fileConfig.resource || (mainDef !== null && it === mainDef.reactorClass.eResource)] - .map [it | JavaGeneratorUtils.getLFResource(it, fileConfig.getSrcGenBasePath(), context, errorReporter)] + .map [it | GeneratorUtils.getLFResource(it, fileConfig.getSrcGenBasePath(), context, errorReporter)] .collect(Collectors.toList()) ) - JavaGeneratorUtils.accommodatePhysicalActionsIfPresent(allResources, target, targetConfig, errorReporter); + GeneratorUtils.accommodatePhysicalActionsIfPresent(allResources, target, targetConfig, errorReporter); // FIXME: Should the GeneratorBase pull in `files` from imported // resources? diff --git a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java b/org.lflang/src/org/lflang/generator/GeneratorUtils.java similarity index 99% rename from org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java rename to org.lflang/src/org/lflang/generator/GeneratorUtils.java index eb8bd28a11..70caa9708b 100644 --- a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java +++ b/org.lflang/src/org/lflang/generator/GeneratorUtils.java @@ -45,9 +45,9 @@ * instead be in GeneratorUtils.kt, but Eclipse cannot * handle Kotlin files. */ -public class JavaGeneratorUtils { +public class GeneratorUtils { - private JavaGeneratorUtils() { + private GeneratorUtils() { // utility class } @@ -299,7 +299,7 @@ public static LFResource getLFResource( LFGeneratorContext context, ErrorReporter errorReporter ) { - TargetDecl target = JavaGeneratorUtils.findTarget(resource); + TargetDecl target = GeneratorUtils.findTarget(resource); KeyValuePairs config = target.getConfig(); var targetConfig = new TargetConfig(); if (config != null) { diff --git a/org.lflang/src/org/lflang/generator/LFGeneratorContext.java b/org.lflang/src/org/lflang/generator/LFGeneratorContext.java index fd01380d0d..e5d39d5fb8 100644 --- a/org.lflang/src/org/lflang/generator/LFGeneratorContext.java +++ b/org.lflang/src/org/lflang/generator/LFGeneratorContext.java @@ -97,7 +97,7 @@ default void finish( Map codeMaps, String interpreter ) { - final boolean isWindows = JavaGeneratorUtils.isHostWindows(); + final boolean isWindows = GeneratorUtils.isHostWindows(); if (execName != null && binPath != null) { Path executable = binPath.resolve(execName + (isWindows && interpreter == null ? ".exe" : "")); String relativeExecutable = fileConfig.srcPkgPath.relativize(executable).toString(); diff --git a/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java b/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java index 4aeef374e7..3621a6eac1 100644 --- a/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java +++ b/org.lflang/src/org/lflang/generator/c/CCmakeCompiler.java @@ -35,7 +35,7 @@ import org.lflang.FileConfig; import org.lflang.TargetConfig; import org.lflang.generator.GeneratorBase; -import org.lflang.generator.JavaGeneratorUtils; +import org.lflang.generator.GeneratorUtils; import org.lflang.generator.LFGeneratorContext; import org.lflang.util.FileUtil; import org.lflang.util.LFCommand; @@ -197,7 +197,7 @@ public LFCommand compileCmakeCommand( FileUtil.toUnixString(fileConfig.getSrcGenPath()) )); - if (JavaGeneratorUtils.isHostWindows()) { + if (GeneratorUtils.isHostWindows()) { arguments.add("-DCMAKE_SYSTEM_VERSION=\"10.0\""); } diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index a2fe0e0f6c..75fb67aab3 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -60,7 +60,7 @@ import org.lflang.generator.CodeBuilder import org.lflang.generator.GeneratorBase import org.lflang.generator.GeneratorResult import org.lflang.generator.IntegratedBuilder -import org.lflang.generator.JavaGeneratorUtils +import org.lflang.generator.GeneratorUtils import org.lflang.generator.LFGeneratorContext import org.lflang.generator.PortInstance import org.lflang.generator.ReactionInstance @@ -366,7 +366,7 @@ class CGenerator extends GeneratorBase { def accommodatePhysicalActionsIfPresent() { // If there are any physical actions, ensure the threaded engine is used and that // keepalive is set to true, unless the user has explicitly set it to false. - for (resource : JavaGeneratorUtils.getResources(reactors)) { + for (resource : GeneratorUtils.getResources(reactors)) { for (action : resource.allContents.toIterable.filter(Action)) { if (action.origin == ActionOrigin.PHYSICAL) { // If the unthreaded runtime is requested, use the threaded runtime instead @@ -389,7 +389,7 @@ class CGenerator extends GeneratorBase { * otherwise report an error and return false. */ protected def boolean isOSCompatible() { - if (JavaGeneratorUtils.isHostWindows) { + if (GeneratorUtils.isHostWindows) { if (isFederated) { errorReporter.reportError( "Federated LF programs with a C target are currently not supported on Windows. " + @@ -549,7 +549,7 @@ class CGenerator extends GeneratorBase { targetConfig.filesNamesWithoutPath.clear(); // Re-apply the cmake-include target property of the main .lf file. - val target = JavaGeneratorUtils.findTarget(mainDef.reactorClass.eResource) + val target = GeneratorUtils.findTarget(mainDef.reactorClass.eResource) if (target.config !== null) { // Update the cmake-include TargetProperty.updateOne( @@ -909,7 +909,7 @@ class CGenerator extends GeneratorBase { } // In case we are in Eclipse, make sure the generated code is visible. - JavaGeneratorUtils.refreshProject(resource, context.mode) + GeneratorUtils.refreshProject(resource, context.mode) } /** diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java index 9a29f27fa9..e52db93876 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java @@ -58,8 +58,8 @@ import org.lflang.generator.CodeBuilder; import org.lflang.generator.CodeMap; import org.lflang.generator.GeneratorResult; +import org.lflang.generator.GeneratorUtils; import org.lflang.generator.IntegratedBuilder; -import org.lflang.generator.JavaGeneratorUtils; import org.lflang.generator.LFGeneratorContext; import org.lflang.generator.ReactorInstance; import org.lflang.generator.SubContext; @@ -633,7 +633,7 @@ public void includeTargetLanguageHeaders() { */ @Override public boolean isOSCompatible() { - if (JavaGeneratorUtils.isHostWindows() && isFederated) { + if (GeneratorUtils.isHostWindows() && isFederated) { errorReporter.reportError( "Federated LF programs with a Python target are currently not supported on Windows. Exiting code generation." ); diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index 244cac7bec..df510f29cd 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -39,7 +39,7 @@ import org.lflang.generator.CodeMap import org.lflang.generator.GeneratorBase import org.lflang.generator.GeneratorResult import org.lflang.generator.IntegratedBuilder -import org.lflang.generator.JavaGeneratorUtils +import org.lflang.generator.GeneratorUtils import org.lflang.generator.LFGeneratorContext import org.lflang.generator.PrependOperator import org.lflang.generator.SubContext @@ -289,7 +289,7 @@ class TSGenerator( private fun compile(resource: Resource, parsingContext: LFGeneratorContext) { - JavaGeneratorUtils.refreshProject(resource, parsingContext.mode) + GeneratorUtils.refreshProject(resource, parsingContext.mode) if (parsingContext.cancelIndicator.isCanceled) return parsingContext.reportProgress("Transpiling to JavaScript...", 70) @@ -331,7 +331,8 @@ class TSGenerator( val ret = pnpmInstall.run(context.cancelIndicator) if (ret != 0) { val errors: String = pnpmInstall.errors.toString() - errorReporter.reportError(JavaGeneratorUtils.findTarget(resource), + errorReporter.reportError( + GeneratorUtils.findTarget(resource), "ERROR: pnpm install command failed" + if (errors.isBlank()) "." else ":\n$errors") } } else { @@ -347,10 +348,10 @@ class TSGenerator( if (npmInstall.run(context.cancelIndicator) != 0) { errorReporter.reportError( - JavaGeneratorUtils.findTarget(resource), + GeneratorUtils.findTarget(resource), "ERROR: npm install command failed: " + npmInstall.errors.toString()) errorReporter.reportError( - JavaGeneratorUtils.findTarget(resource), "ERROR: npm install command failed." + + GeneratorUtils.findTarget(resource), "ERROR: npm install command failed." + "\nFor installation instructions, see: https://www.npmjs.com/get-npm") return } @@ -478,7 +479,7 @@ class TSGenerator( } private fun isOsCompatible(): Boolean { - if (isFederated && JavaGeneratorUtils.isHostWindows()) { + if (isFederated && GeneratorUtils.isHostWindows()) { errorReporter.reportError( "Federated LF programs with a TypeScript target are currently not supported on Windows. Exiting code generation." ) From b12df019104b49bc305d575e4faad5abd329fea3 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Mon, 28 Feb 2022 09:27:06 +0100 Subject: [PATCH 30/30] fix compilation error in tests --- .../src/org/lflang/tests/runtime/CSchedulerTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/runtime/CSchedulerTest.java b/org.lflang.tests/src/org/lflang/tests/runtime/CSchedulerTest.java index c4fa0a2b2b..d9ab56ed30 100644 --- a/org.lflang.tests/src/org/lflang/tests/runtime/CSchedulerTest.java +++ b/org.lflang.tests/src/org/lflang/tests/runtime/CSchedulerTest.java @@ -59,8 +59,7 @@ private void runTest(SchedulerOption scheduler, EnumSet categories Message.DESC_SCHED_SWAPPING + scheduler.toString() +".", categories::contains, test -> { - test.getContext() - .getArgs() + test.context.getArgs() .setProperty( "scheduler", scheduler.toString()