From 602054888d037552d88186b530fa4ad624fc3e6c Mon Sep 17 00:00:00 2001
From: Christian Menard <christian.menard@tu-dresden.de>
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<Path, CodeMap>, pathRelativeToOutDir: String, f: Emitter.() -> Unit): Unit
-        = emit(codeMaps, srcGenPath.resolve(pathRelativeToOutDir), f)
-
+    inline fun emit(codeMaps: MutableMap<Path, CodeMap>, pathRelativeToOutDir: String, f: Emitter.() -> Unit): Unit =
+        emit(codeMaps, getSrcGenPath().resolve(pathRelativeToOutDir), f)
 }
 
 /**

From 1e164d7e0bc9b8a27e6430d34c3071fc33bbce09 Mon Sep 17 00:00:00 2001
From: Christian Menard <christian.menard@tu-dresden.de>
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 <christian.menard@tu-dresden.de>
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 <christian.menard@tu-dresden.de>
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<Path> 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<LFCommand> 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 <christian.menard@tu-dresden.de>
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<String>()
 
     // //////////////////////////////////////////
-    // // 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 <christian.menard@tu-dresden.de>
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 <christian.menard@tu-dresden.de>
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 <hokeunkim@berkeley.edu>}
  */
 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 <christian.menard@tu-dresden.de>
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<LFTest> {
     /** 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<LFTest> 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 <christian.menard@tu-dresden.de>
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 <christian.menard@tu-dresden.de>
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 <christian.menard@tu-dresden.de>
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<String> 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<String> 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 <christian.menard@tu-dresden.de>
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 <christian.menard@tu-dresden.de>
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<Path> 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<String> 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<String> arguments =  new ArrayList<String>();
-        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<String> 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 <christian.menard@tu-dresden.de>
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<String> files) throws IOException {
+    public static void copyFilesFromClassPath(String srcDir, Path dstDir, List<String> 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<String> 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 <christian.menard@tu-dresden.de>
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<Path> 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<JarEntry> 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<String> 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<Path> 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<Path>(
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<Path, CodeMap>, 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<Path> 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<JarEntry> 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<String> 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<Path> 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 <christian.menard@tu-dresden.de>
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 <christian.menard@tu-dresden.de>
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<String> 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<Strin
         }
     }
 
-    /**
-    * 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.
      *

From e246f92848d51b2ecb965957f0bc5fa2174fc796 Mon Sep 17 00:00:00 2001
From: Christian Menard <christian.menard@tu-dresden.de>
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 <christian.menard@tu-dresden.de>
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 <christian.menard@tu-dresden.de>
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 <christian.menard@tu-dresden.de>
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 <christian.menard@tu-dresden.de>
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 <christian.menard@tu-dresden.de>
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 <christian.menard@tu-dresden.de>
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 <christian.menard@tu-dresden.de>
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 <christian.menard@tu-dresden.de>
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 <christian.menard@tu-dresden.de>
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 <soroush@utdallas.edu>}
- */
-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<String>()
-
-    // //////////////////////////////////////////
-    /**
-     * 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<String> getPythonInitializerList(StateVar state) {
-        if (!state.isInitialized) {
-            return null
-        }
-
-        var list = new ArrayList<String>();
-
-        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<String>();
-        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<String>()
-
-        // 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 (<code>inits<code>) the code to
-     * initialize local variable for <code>port<code> 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 <code>inits<code>.
-     */
-    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<String>()
-        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<String> 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 <code>instance<code>. Will recursively do 
-     * the same for the children of <code>instance<code> 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 <code>instance<code> (or any of its children) belong to 
-     *  <code>federate<code> 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<Model>
-
-        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<String> 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<Path, CodeMap>
-        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 <code>reaction<code> that belongs to reactor 
-     * <code>decl<code>.
-     * 
-     * @param reaction The reaction to generate Python-specific initialization for.
-     * @param decl The reactor to which <code>reaction<code> 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 <code>Py_BuildValue<code> 
-     *  (@see <a href="https://docs.python.org/3/c-api/arg.html#c.Py_BuildValue">docs.python.org/3/c-api</a>).
-     * @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<Action>();
-        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 <code>port<code> 
-     * (which could be a multiport with a width determined by <code>widthSpec<code>).
-     * 
-     * This is to accommodate reactions like <code>reaction() -> s.out<code> where s is a bank. In this example,
-     * the generated Python function will have the signature <code>reaction_function_0(self, s_out)<code>, where
-     * s_out is a list of out ports. This will later be turned into the proper <code>s.out<code> 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 <christian.menard@tu-dresden.de>
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<Path, CodeMap> generatePythonFiles(FederateInstance federate) throws
         }
         Map<Path, CodeMap> 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<Path, CodeMap> 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 <christian.menard@tu-dresden.de>
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<Path, CodeMap> 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 <christian.menard@tu-dresden.de>
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<TestCategory> categories
             Message.DESC_SCHED_SWAPPING + scheduler.toString() +".",
             categories::contains,
             test -> {
-                test.getContext()
-                    .getArgs()
+                test.context.getArgs()
                     .setProperty(
                         "scheduler",
                         scheduler.toString()