diff --git a/.github/workflows/all-misc.yml b/.github/workflows/all-misc.yml
index 7955c3c6ed..45349e1513 100644
--- a/.github/workflows/all-misc.yml
+++ b/.github/workflows/all-misc.yml
@@ -21,31 +21,3 @@ concurrency:
jobs:
check-diff:
uses: ./.github/workflows/check-diff.yml
-
- # Test the Gradle build.
- building:
- needs: check-diff
- uses: ./.github/workflows/build.yml
- with:
- all-platforms: ${{ !github.event.pull_request.draft }}
- if: ${{ needs.check-diff.outputs.run_build == 'true' }}
-
- # Run tests for the standalone compiler.
- cli:
- if: ${{ needs.check-diff.outputs.run_misc == 'true' }}
- needs: check-diff
- uses: ./.github/workflows/cli-tests.yml
- with:
- all-platforms: ${{ !github.event.pull_request.draft }}
-
- # Run language server tests.
- lsp:
- if: ${{ needs.check-diff.outputs.run_misc == 'true' }}
- needs: check-diff
- uses: ./.github/workflows/lsp-tests.yml
- with:
- all-platforms: ${{ !github.event.pull_request.draft }}
-
- check-labels:
- uses: ./.github/workflows/check-labels.yml
- if: ${{ github.event_name == 'pull_request' }}
diff --git a/.github/workflows/all-targets.yml b/.github/workflows/all-targets.yml
index 4068bc2576..be5f0dff5c 100644
--- a/.github/workflows/all-targets.yml
+++ b/.github/workflows/all-targets.yml
@@ -19,35 +19,5 @@ concurrency:
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
- check-diff:
- uses: ./.github/workflows/check-diff.yml
-
c:
uses: ./.github/workflows/only-c.yml
- needs: check-diff
- if: ${{ needs.check-diff.outputs.run_c == 'true' }}
-
- cpp:
- uses: ./.github/workflows/only-cpp.yml
- needs: check-diff
- if: ${{ needs.check-diff.outputs.run_cpp == 'true' }}
-
- py:
- uses: ./.github/workflows/only-py.yml
- needs: check-diff
- if: ${{ needs.check-diff.outputs.run_py == 'true' || needs.check-diff.outputs.run_c == 'true' }}
-
- rs:
- uses: ./.github/workflows/only-rs.yml
- needs: check-diff
- if: ${{ needs.check-diff.outputs.run_rs == 'true' }}
-
- ts:
- uses: ./.github/workflows/only-ts.yml
- needs: check-diff
- if: ${{ needs.check-diff.outputs.run_ts == 'true' }}
-
- serialization:
- if: ${{ needs.check-diff.outputs.run_c == 'true' || needs.check-diff.outputs.run_py == 'true' || needs.check-diff.outputs.run_ts == 'true' }}
- needs: check-diff
- uses: ./.github/workflows/serialization-tests.yml
diff --git a/.github/workflows/c-tests-with-rust-rti.yml b/.github/workflows/c-tests-with-rust-rti.yml
new file mode 100644
index 0000000000..8a300ea859
--- /dev/null
+++ b/.github/workflows/c-tests-with-rust-rti.yml
@@ -0,0 +1,48 @@
+name: C tests
+
+on:
+ workflow_call:
+ inputs:
+ compiler-ref:
+ required: false
+ type: string
+ runtime-ref:
+ required: false
+ type: string
+ use-cpp:
+ required: false
+ type: boolean
+ default: false
+ scheduler:
+ required: false
+ type: string
+ all-platforms:
+ required: false
+ default: true
+ type: boolean
+
+jobs:
+ regular-tests:
+ strategy:
+ matrix:
+ platform: ${{ (inputs.all-platforms && fromJSON('["ubuntu-latest", "macos-latest", "windows-latest"]')) || fromJSON('["ubuntu-latest"]') }}
+ runs-on: ${{ matrix.platform }}
+ timeout-minutes: 120
+ steps:
+ - name: Check out lingua-franca repository
+ uses: actions/checkout@v3
+ with:
+ repository: chanijjani/lingua-franca
+ submodules: true
+ ref: ${{ inputs.compiler-ref }}
+ fetch-depth: 0
+ - name: Prepare build environment
+ uses: ./.github/actions/prepare-build-env
+ - name: Perform tests for C target with default scheduler
+ run: ./gradlew targetTest -Ptarget=C
+ if: ${{ !inputs.use-cpp && !inputs.scheduler }}
+ - name: Perform tests for C target with specified scheduler (no LSP tests)
+ run: |
+ echo "Specified scheduler: ${{ inputs.scheduler }}"
+ ./gradlew targetTest -Ptarget=C -Dscheduler=${{ inputs.scheduler }}
+ if: ${{ !inputs.use-cpp && inputs.scheduler }}
diff --git a/.github/workflows/only-c.yml b/.github/workflows/only-c.yml
index ae9016465c..9a27268332 100644
--- a/.github/workflows/only-c.yml
+++ b/.github/workflows/only-c.yml
@@ -13,35 +13,8 @@ concurrency:
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
jobs:
- # Run the C integration tests.
+ # Run the C integration tests with Rust RTI.
default:
- uses: ./.github/workflows/c-tests.yml
+ uses: ./.github/workflows/c-tests-with-rust-rti.yml
with:
all-platforms: ${{ !github.event.pull_request.draft }}
-
- # Run the C benchmark tests.
- benchmarking:
- uses: lf-lang/benchmarks-lingua-franca/.github/workflows/benchmark-tests.yml@main
- with:
- target: "C"
-
- # Run the C Arduino integration tests.
- arduino:
- uses: ./.github/workflows/c-arduino-tests.yml
- with:
- all-platforms: ${{ !github.event.pull_request.draft }}
-
- # Run the C Zephyr integration tests.
- zephyr:
- uses: ./.github/workflows/c-zephyr-tests.yml
-
- # Run the CCpp integration tests.
- ccpp:
- uses: ./.github/workflows/c-tests.yml
- with:
- use-cpp: true
- all-platforms: ${{ !github.event.pull_request.draft }}
-
- # Run the Uclid-based LF Verifier benchmarks.
- verifier:
- uses: ./.github/workflows/c-verifier-tests.yml
diff --git a/core/src/integrationTest/java/org/lflang/tests/RuntimeTest.java b/core/src/integrationTest/java/org/lflang/tests/RuntimeTest.java
index 12c71eed7c..0c588719c8 100644
--- a/core/src/integrationTest/java/org/lflang/tests/RuntimeTest.java
+++ b/core/src/integrationTest/java/org/lflang/tests/RuntimeTest.java
@@ -1,5 +1,6 @@
package org.lflang.tests;
+import java.nio.file.Path;
import java.util.EnumSet;
import java.util.List;
import org.junit.jupiter.api.Assumptions;
@@ -142,6 +143,21 @@ public void runFederatedTests() {
false);
}
+ @Test
+ // public void runFederatedTestsWithRustRti(Path rustRtiProjectPath) {
+ public void runFederatedTestsWithRustRti() {
+ Assumptions.assumeTrue(supportsFederatedExecution(), Message.NO_FEDERATION_SUPPORT);
+ runTestsForTargetsWithRustRti(
+ Message.DESC_FEDERATED,
+ TestCategory.FEDERATED::equals,
+ Transformers::noChanges,
+ Configurators::noChanges,
+ TestLevel.EXECUTION,
+ // false,
+ // rustRtiProjectPath);
+ false);
+ }
+
/** Run the tests for modal reactors. */
@Test
public void runModalTests() {
diff --git a/core/src/integrationTest/java/org/lflang/tests/runtime/RustRtiCTest.java b/core/src/integrationTest/java/org/lflang/tests/runtime/RustRtiCTest.java
new file mode 100644
index 0000000000..e9c7b94846
--- /dev/null
+++ b/core/src/integrationTest/java/org/lflang/tests/runtime/RustRtiCTest.java
@@ -0,0 +1,104 @@
+/*************
+ * Copyright (c) 2019, The University of California at Berkeley.
+ *
+ * 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.tests.runtime;
+
+import java.nio.file.Path;
+
+import org.junit.jupiter.api.Assumptions;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.lflang.target.Target;
+import org.lflang.tests.RuntimeTest;
+
+/**
+ * Collection of tests for the C target.
+ *
+ *
Tests that are implemented in the base class are still overridden so that each test can be
+ * easily invoked individually from IDEs with JUnit support like Eclipse and IntelliJ. This is
+ * typically done by right-clicking on the name of the test method and then clicking "Run".*
+ *
+ * @author Marten Lohstroh
+ * @author Chanhee Lee
+ */
+public class RustRtiCTest extends RuntimeTest {
+
+ public RustRtiCTest() {
+ super(Target.C);
+ }
+
+ @Override
+ protected boolean supportsSingleThreadedExecution() {
+ return true;
+ }
+
+ @Override
+ protected boolean supportsFederatedExecution() {
+ return true;
+ }
+
+ @Override
+ protected boolean supportsDockerOption() {
+ return true;
+ }
+
+ @Test
+ @Override
+ public void runBasicTests() {
+ super.runBasicTests();
+ }
+
+ @Test
+ @Override
+ public void runGenericsTests() {
+ super.runGenericsTests();
+ }
+
+ @Test
+ @Override
+ public void runTargetSpecificTests() {
+ Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT);
+ super.runTargetSpecificTests();
+ }
+
+ @Test
+ @Override
+ public void runMultiportTests() {
+ super.runMultiportTests();
+ }
+
+ @Test
+ @Override
+ public void runWithThreadingOff() {
+ super.runWithThreadingOff();
+ }
+
+ @Test
+ // public void runFederatedTests(Path rustRtiProjectPath) {
+ public void runFederatedTests() {
+ Assumptions.assumeFalse(isWindows(), Message.NO_WINDOWS_SUPPORT);
+ // super.runFederatedTestsWithRustRti(rustRtiProjectPath);
+ super.runFederatedTestsWithRustRti();
+ }
+}
diff --git a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java
index 0acf495fc7..ed1d23d908 100644
--- a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java
+++ b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java
@@ -31,6 +31,7 @@
import org.lflang.ast.ASTUtils;
import org.lflang.federated.launcher.FedLauncherGenerator;
import org.lflang.federated.launcher.RtiConfig;
+import org.lflang.federated.launcher.RustRtiConfig;
import org.lflang.generator.CodeMap;
import org.lflang.generator.GeneratorArguments;
import org.lflang.generator.GeneratorResult.Status;
@@ -201,6 +202,98 @@ public boolean doGenerate(Resource resource, LFGeneratorContext context) throws
return false;
}
+ /**
+ * Produce LF code for each federate in a separate file, then invoke a target-specific code
+ * generator for each of those files.
+ *
+ * @param resource The resource that has the federated main reactor in it
+ * @param context The context in which to carry out the code generation.
+ * @return False if no errors have occurred, true otherwise.
+ */
+ // public boolean doGenerateForRustRTI(Resource resource, LFGeneratorContext context, Path rustRtiProjectPath) throws IOException {
+ public boolean doGenerateForRustRTI(Resource resource, LFGeneratorContext context) throws IOException {
+ if (!federatedExecutionIsSupported(resource)) return true;
+ cleanIfNeeded(context);
+
+ // In a federated execution, we need keepalive to be true,
+ // otherwise a federate could exit simply because it hasn't received
+ // any messages.
+ KeepaliveProperty.INSTANCE.override(targetConfig, true);
+
+ // Process command-line arguments
+ processCLIArguments(context);
+
+ // Find the federated reactor
+ Reactor federation = FedASTUtils.findFederatedReactor(resource);
+
+ // Make sure the RTI host is set correctly.
+ setRTIHost(federation);
+
+ // Create the FederateInstance objects.
+ ReactorInstance main = createFederateInstances(federation, context);
+
+ // Insert reactors that split multiports into many ports.
+ insertIndexers(main, resource);
+
+ // Clear banks so that each bank member becomes a single federate.
+ for (Instantiation instantiation : ASTUtils.allInstantiations(federation)) {
+ instantiation.setWidthSpec(null);
+ instantiation.setWidthSpec(null);
+ }
+
+ // Find all the connections between federates.
+ // For each connection between federates, replace it in the
+ // AST with an action (which inherits the delay) and three reactions.
+ // The action will be physical for physical connections and logical
+ // for logical connections.
+ replaceFederateConnectionsWithProxies(federation, main, resource);
+
+ FedEmitter fedEmitter =
+ new FedEmitter(
+ fileConfig,
+ ASTUtils.toDefinition(mainDef.getReactorClass()),
+ messageReporter,
+ rtiConfig);
+
+ // Generate LF code for each federate.
+ Map lf2lfCodeMapMap = new HashMap<>();
+ for (FederateInstance federate : federates) {
+ lf2lfCodeMapMap.putAll(fedEmitter.generateFederate(context, federate, federates.size()));
+ }
+
+ // Do not invoke target code generators if --no-compile flag is used.
+ if (context.getTargetConfig().get(NoCompileProperty.INSTANCE)) {
+ context.finish(Status.GENERATED, lf2lfCodeMapMap);
+ return false;
+ }
+
+ // If the RTI is to be built locally, set up a build environment for it.
+ prepareRtiBuildEnvironment(context);
+
+ Map codeMapMap =
+ compileFederates(
+ context,
+ lf2lfCodeMapMap,
+ subContexts -> {
+ createDockerFiles(context, subContexts);
+ // generateLaunchScriptForRustRti(rustRtiProjectPath);
+ generateLaunchScriptForRustRti();
+ // If an error has occurred during codegen of any federate, report it.
+ subContexts.forEach(
+ c -> {
+ if (c.getErrorReporter().getErrorsOccurred()) {
+ context
+ .getErrorReporter()
+ .at(c.getFileConfig().srcFile)
+ .error("Failure during code generation of " + c.getFileConfig().srcFile);
+ }
+ });
+ });
+
+ context.finish(Status.COMPILED, codeMapMap);
+ return false;
+ }
+
/**
* Prepare a build environment for the rti alongside the generated sources of the federates.
*
@@ -229,6 +322,13 @@ private void generateLaunchScript() {
.doGenerate(federates, rtiConfig);
}
+ // private void generateLaunchScriptForRustRti(Path rustRtiProjectPath) {
+ private void generateLaunchScriptForRustRti() {
+ new FedLauncherGenerator(this.targetConfig, this.fileConfig, this.messageReporter)
+ // .doGenerateForRustRTI(federates, new RustRtiConfig(rustRtiProjectPath));
+ .doGenerateForRustRTI(federates, new RustRtiConfig());
+ }
+
/**
* Generate a Dockerfile for each federate and a docker-compose.yml for the federation.
*
diff --git a/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java b/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java
index 5ec59ecb34..4605635967 100644
--- a/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java
+++ b/core/src/main/java/org/lflang/federated/launcher/FedLauncherGenerator.java
@@ -265,6 +265,172 @@ public void doGenerate(List federates, RtiConfig rtiConfig) {
}
}
+ /**
+ * Create the launcher shell scripts. This will create one or two files in the output path (bin
+ * directory). The first has name equal to the filename of the source file without the ".lf"
+ * extension. This will be a shell script that launches the RTI and the federates. If, in
+ * addition, either the RTI or any federate is mapped to a particular machine (anything other than
+ * the default "localhost" or "0.0.0.0"), then this will generate a shell script in the bin
+ * directory with name filename_distribute.sh that copies the relevant source files to the remote
+ * host and compiles them so that they are ready to execute using the launcher.
+ *
+ * A precondition for this to work is that the user invoking this code generator can log into
+ * the remote host without supplying a password. Specifically, you have to have installed your
+ * public key (typically found in ~/.ssh/id_rsa.pub) in ~/.ssh/authorized_keys on the remote host.
+ * In addition, the remote host must be running an ssh service. On an Arch Linux system using
+ * systemd, for example, this means running:
+ *
+ *
sudo systemctl ssh.service
+ *
+ * Enable means to always start the service at startup, whereas start means to just start it
+ * this once.
+ *
+ * @param federates A list of federate instances in the federation
+ * @param rustRtiConfig Can have values for 'host', 'dir', 'user', and 'projectPath'
+ */
+ public void doGenerateForRustRTI(List federates, RustRtiConfig rustRtiConfig) {
+ // NOTE: It might be good to use screen when invoking the RTI
+ // or federates remotely, so you can detach and the process keeps running.
+ // However, I was unable to get it working properly.
+ // What this means is that the shell that invokes the launcher
+ // needs to remain live for the duration of the federation.
+ // If that shell is killed, the federation will die.
+ // Hence, it is reasonable to launch the federation on a
+ // machine that participates in the federation, for example,
+ // on the machine that runs the RTI. The command I tried
+ // to get screen to work looks like this:
+ // ssh -t «target» cd «path»; screen -S «filename»_«federate.name» -L
+ // bin/«filename»_«federate.name» 2>&1
+ // var outPath = binGenPath
+ StringBuilder shCode = new StringBuilder();
+ StringBuilder distCode = new StringBuilder();
+ shCode.append(getSetupCode()).append("\n");
+ String distHeader = getDistHeader();
+ String host = rustRtiConfig.getHost();
+ String target = host;
+
+ String user = rustRtiConfig.getUser();
+ if (user != null) {
+ target = user + "@" + host;
+ }
+
+ shCode.append("#### Host is ").append(host);
+
+ // Launch the RTI in the foreground.
+ if (host.equals("localhost") || host.equals("0.0.0.0")) {
+ // FIXME: the paths below will not work on Windows
+ shCode.append(getLaunchCodeForRustRti(getRtiCommand(federates, false), rustRtiConfig.getProjectPath())).append("\n");
+ } else {
+ // Start the RTI on the remote machine - Not supported yet for Rust RTI.
+ }
+
+ // Index used for storing pids of federates
+ int federateIndex = 0;
+ for (FederateInstance federate : federates) {
+ var buildConfig = getBuildConfig(federate, fileConfig, messageReporter);
+ if (federate.isRemote) {
+ Path fedRelSrcGenPath =
+ fileConfig.getOutPath().relativize(fileConfig.getSrcGenPath()).resolve(federate.name);
+ if (distCode.isEmpty()) distCode.append(distHeader).append("\n");
+ String logFileName = String.format("log/%s_%s.log", fileConfig.name, federate.name);
+ distCode.append(getDistCode(rustRtiConfig.getDirectory(), federate)).append("\n");
+ shCode
+ .append(getFedRemoteLaunchCode(rustRtiConfig.getDirectory(), federate, federateIndex++))
+ .append("\n");
+ } else {
+ String executeCommand = buildConfig.localExecuteCommand();
+ shCode
+ .append(getFedLocalLaunchCode(federate, executeCommand, federateIndex++))
+ .append("\n");
+ }
+ }
+ if (host.equals("localhost") || host.equals("0.0.0.0")) {
+ // Local PID managements
+ shCode.append(
+ "echo \"#### Bringing the RTI back to foreground so it can receive Control-C.\"" + "\n");
+ shCode.append("fg %1" + "\n");
+ }
+ // Wait for launched processes to finish
+ shCode
+ .append(
+ String.join(
+ "\n",
+ "echo \"RTI has exited. Wait for federates to exit.\"",
+ "# Wait for launched processes to finish.",
+ "# The errors are handled separately via trap.",
+ "for pid in \"${pids[@]}\"",
+ "do",
+ " wait $pid || exit $?",
+ "done",
+ "echo \"All done.\"",
+ "EXITED_SUCCESSFULLY=true"))
+ .append("\n");
+
+ // Create bin directory for the script.
+ if (!Files.exists(fileConfig.binPath)) {
+ try {
+ Files.createDirectories(fileConfig.binPath);
+ } catch (IOException e) {
+ messageReporter.nowhere().error("Unable to create directory: " + fileConfig.binPath);
+ }
+ }
+
+ // Write the launcher file.
+ File file = fileConfig.binPath.resolve(fileConfig.name).toFile();
+ messageReporter.nowhere().info("Script for launching the federation: " + file);
+
+ // Delete file previously produced, if any.
+ if (file.exists()) {
+ if (!file.delete())
+ messageReporter
+ .nowhere()
+ .error("Failed to delete existing federated launch script \"" + file + "\"");
+ }
+
+ FileOutputStream fOut = null;
+ try {
+ fOut = new FileOutputStream(file);
+ } catch (FileNotFoundException e) {
+ messageReporter.nowhere().error("Unable to find file: " + file);
+ }
+ if (fOut != null) {
+ try {
+ fOut.write(shCode.toString().getBytes());
+ fOut.close();
+ } catch (IOException e) {
+ messageReporter.nowhere().error("Unable to write to file: " + file);
+ }
+ }
+
+ if (!file.setExecutable(true, false)) {
+ messageReporter.nowhere().warning("Unable to make launcher script executable.");
+ }
+
+ // Write the distributor file.
+ // Delete the file even if it does not get generated.
+ file = fileConfig.binPath.resolve(fileConfig.name + "_distribute.sh").toFile();
+ if (file.exists()) {
+ if (!file.delete())
+ messageReporter
+ .nowhere()
+ .error("Failed to delete existing federated distributor script \"" + file + "\"");
+ }
+ if (distCode.length() > 0) {
+ try {
+ fOut = new FileOutputStream(file);
+ fOut.write(distCode.toString().getBytes());
+ fOut.close();
+ if (!file.setExecutable(true, false)) {
+ messageReporter.nowhere().warning("Unable to make file executable: " + file);
+ }
+ } catch (FileNotFoundException e) {
+ messageReporter.nowhere().error("Unable to find file: " + file);
+ } catch (IOException e) {
+ messageReporter.nowhere().error("Unable to write to file " + file);
+ }
+ }
+ }
+
private String getSetupCode() {
return String.join(
"\n",
@@ -377,6 +543,36 @@ private String getLaunchCode(String rtiLaunchCode) {
"sleep 1");
}
+ private String getLaunchCodeForRustRti(String rtiLaunchCode, Path rustRtiProjectPath) {
+ String launchCodeWithLogging = String.join(" ", rtiLaunchCode, ">& RTI.log &");
+ String launchCodeWithoutLogging = String.join(" ", rtiLaunchCode, "&");
+ return String.join(
+ "\n",
+ "echo \"#### Launching the Rust runtime infrastructure (RTI).\"",
+ "# The Rust RTI is started first to allow proper boot-up",
+ "# before federates will try to connect.",
+ "# The RTI will be brought back to foreground",
+ "# to be responsive to user inputs after all federates",
+ "# are launched.",
+ "LF_RUST_RTI_PATH=\"find ~/ -name lf-rust-rti\"",
+ "cd %{LF_RUST_RTI_PATH}",
+ "cargo run -- -i ${FEDERATION_ID} \\",
+ " -n 2 \\",
+ " -c init \\",
+ "&",
+ "if [ \"$1\" = \"-l\" ]; then",
+ launchCodeWithLogging,
+ "else",
+ launchCodeWithoutLogging,
+ "fi",
+ "# Store the PID of the RTI",
+ "RTI=$!",
+ "# Wait for the RTI to boot up before",
+ "# starting federates (this could be done by waiting for a specific output",
+ "# from the RTI, but here we use sleep)",
+ "sleep 1");
+ }
+
private String getRemoteLaunchCode(
Object host, Object target, String logFileName, String rtiLaunchString) {
return String.join(
diff --git a/core/src/main/java/org/lflang/federated/launcher/RustRtiConfig.java b/core/src/main/java/org/lflang/federated/launcher/RustRtiConfig.java
new file mode 100644
index 0000000000..c5a7f38f12
--- /dev/null
+++ b/core/src/main/java/org/lflang/federated/launcher/RustRtiConfig.java
@@ -0,0 +1,29 @@
+package org.lflang.federated.launcher;
+
+import java.nio.file.Path;
+
+/**
+ * Class for storing configuration settings pertaining to the Rust RTI.
+ *
+ * @author Chanhee Lee
+ */
+public class RustRtiConfig extends RtiConfig {
+
+ private Path projectPath;
+
+ /** Construct a new RTI configuration with all options set to their defaults. */
+ public RustRtiConfig() {
+ super();
+ }
+
+ /** Construct a new RTI configuration with all options set to their defaults. */
+ public RustRtiConfig(Path projectPath) {
+ super();
+ this.projectPath = projectPath;
+ }
+
+ /** Return the Rust RTI project path. */
+ public Path getProjectPath() {
+ return projectPath;
+ }
+}
diff --git a/core/src/main/java/org/lflang/generator/LFGenerator.java b/core/src/main/java/org/lflang/generator/LFGenerator.java
index f4e61e93d5..72d17e647f 100644
--- a/core/src/main/java/org/lflang/generator/LFGenerator.java
+++ b/core/src/main/java/org/lflang/generator/LFGenerator.java
@@ -121,6 +121,43 @@ public void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorCont
}
}
+ // public void doGenerateForRustRTI(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context, Path rustRtiProjectPath) {
+ public void doGenerateForRustRTI(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) {
+ assert injector != null;
+ final LFGeneratorContext lfContext;
+ if (context instanceof LFGeneratorContext) {
+ lfContext = (LFGeneratorContext) context;
+ } else {
+ lfContext = LFGeneratorContext.lfGeneratorContextOf(resource, fsa, context);
+ }
+
+ // The fastest way to generate code is to not generate any code.
+ if (lfContext.getMode() == LFGeneratorContext.Mode.LSP_FAST) return;
+
+ if (FedASTUtils.findFederatedReactor(resource) != null) {
+ try {
+ FedGenerator fedGenerator = new FedGenerator(lfContext);
+ injector.injectMembers(fedGenerator);
+ // generatorErrorsOccurred = fedGenerator.doGenerateForRustRTI(resource, lfContext, rustRtiProjectPath);
+ generatorErrorsOccurred = fedGenerator.doGenerateForRustRTI(resource, lfContext);
+ } catch (IOException e) {
+ throw new RuntimeIOException("Error during federated code generation", e);
+ }
+
+ } else {
+ final GeneratorBase generator = createGenerator(lfContext);
+
+ if (generator != null) {
+ generator.doGenerate(resource, lfContext);
+ generatorErrorsOccurred = generator.errorsOccurred();
+ }
+ }
+ final MessageReporter messageReporter = lfContext.getErrorReporter();
+ if (messageReporter instanceof LanguageServerMessageReporter) {
+ ((LanguageServerMessageReporter) messageReporter).publishDiagnostics();
+ }
+ }
+
/** Return true if errors occurred in the last call to doGenerate(). */
public boolean errorsOccurred() {
return generatorErrorsOccurred;
diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c
index d96cbceeac..26eb3465c5 160000
--- a/core/src/main/resources/lib/c/reactor-c
+++ b/core/src/main/resources/lib/c/reactor-c
@@ -1 +1 @@
-Subproject commit d96cbceeac172a3c4ff9caa4520bd0b3034c713e
+Subproject commit 26eb3465c5ca425a03ded2e042a7b6b35cf0181f
diff --git a/core/src/testFixtures/java/org/lflang/tests/TestBase.java b/core/src/testFixtures/java/org/lflang/tests/TestBase.java
index 6923149f07..61be6685b5 100644
--- a/core/src/testFixtures/java/org/lflang/tests/TestBase.java
+++ b/core/src/testFixtures/java/org/lflang/tests/TestBase.java
@@ -196,6 +196,39 @@ protected final void runTestsAndPrintResults(
}
}
+ /**
+ * Run selected tests for a given target and configurator up to the specified level.
+ *
+ * @param target The target to run tests for.
+ * @param selected A predicate that given a test category returns whether it should be included in
+ * this test run or not.
+ * @param configurator A procedure for configuring the tests.
+ * @param copy Whether to work on copies of tests in the test. registry.
+ */
+ protected final void runTestsAndPrintResultsWithRustRti(
+ Target target,
+ Predicate selected,
+ TestLevel level,
+ Transformer transformer,
+ Configurator configurator,
+ // boolean copy,
+ // Path rustRtiProjectPath) {
+ boolean copy) {
+ var categories = Arrays.stream(TestCategory.values()).filter(selected).toList();
+ for (var category : categories) {
+ System.out.println(category.getHeader());
+ var tests = testRegistry.getRegisteredTests(target, category, copy);
+ try {
+ // validateAndRunWithRustRti(tests, transformer, configurator, level, rustRtiProjectPath);
+ validateAndRunWithRustRti(tests, transformer, configurator, level);
+ } catch (IOException e) {
+ throw new RuntimeIOException(e);
+ }
+ System.out.println(testRegistry.getCoverageReport(target, category));
+ checkAndReportFailures(tests);
+ }
+ }
+
/**
* Run tests in the given selection for all targets enabled in this class.
*
@@ -217,6 +250,30 @@ protected void runTestsForTargets(
}
}
+ /**
+ * Run tests in the given selection for all targets enabled in this class.
+ *
+ * @param description A string that describes the collection of tests.
+ * @param selected A predicate that given a test category returns whether it should be included in
+ * this test run or not.
+ * @param configurator A procedure for configuring the tests.
+ * @param copy Whether to work on copies of tests in the test. registry.
+ */
+ protected void runTestsForTargetsWithRustRti(
+ String description,
+ Predicate selected,
+ Transformer transformer,
+ Configurator configurator,
+ TestLevel level,
+ // boolean copy,
+ // Path rustRtiProjectPath) {
+ boolean copy) {
+ for (Target target : this.targets) {
+ // runTestsForRustRti(List.of(target), description, selected, transformer, configurator, level, copy, rustRtiProjectPath);
+ runTestsForRustRti(List.of(target), description, selected, transformer, configurator, level, copy);
+ }
+ }
+
/**
* Run tests in the given selection for a subset of given targets.
*
@@ -241,6 +298,33 @@ protected void runTestsFor(
}
}
+ /**
+ * Run tests in the given selection for a subset of given targets.
+ *
+ * @param subset The subset of targets to run the selected tests for.
+ * @param description A string that describes the collection of tests.
+ * @param selected A predicate that given a test category returns whether it should be included in
+ * this test run or not.
+ * @param configurator A procedure for configuring the tests.
+ * @param copy Whether to work on copies of tests in the test. registry.
+ */
+ protected void runTestsForRustRti(
+ List subset,
+ String description,
+ Predicate selected,
+ Transformer transformer,
+ Configurator configurator,
+ TestLevel level,
+ // boolean copy,
+ // Path rustRtiProjectPath) {
+ boolean copy) {
+ for (Target target : subset) {
+ printTestHeader(target, description);
+ // runTestsAndPrintResultsWithRustRti(target, selected, level, transformer, configurator, copy, rustRtiProjectPath);
+ runTestsAndPrintResultsWithRustRti(target, selected, level, transformer, configurator, copy);
+ }
+ }
+
/** Whether to enable threading. */
protected boolean supportsSingleThreadedExecution() {
return false;
@@ -496,6 +580,27 @@ private void generateCode(LFTest test) throws TestError {
}
}
+ /**
+ * Invoke the code generator for the given test.
+ *
+ * @param test The test to generate code for.
+ */
+ // private void generateCodeForRustRti(LFTest test, Path rustRtiProjectPath) throws TestError {
+ private void generateCodeForRustRti(LFTest test) throws TestError {
+ if (test.getFileConfig().resource == null) {
+ test.getContext().finish(GeneratorResult.NOTHING);
+ }
+ try {
+ // generator.doGenerateForRustRTI(test.getFileConfig().resource, fileAccess, test.getContext(), rustRtiProjectPath);
+ generator.doGenerateForRustRTI(test.getFileConfig().resource, fileAccess, test.getContext());
+ } catch (Throwable e) {
+ throw new TestError("Code generation unsuccessful.", Result.CODE_GEN_FAIL, e);
+ }
+ if (generator.errorsOccurred()) {
+ throw new TestError("Code generation unsuccessful.", Result.CODE_GEN_FAIL);
+ }
+ }
+
/**
* Given an indexed test, execute it and label the test as failing if it did not execute, took too
* long to execute, or executed but exited with an error code.
@@ -712,4 +817,51 @@ private void validateAndRun(
System.out.print(System.lineSeparator());
}
+
+ /**
+ * Validate and run the given tests, using the specified configuratator and level.
+ *
+ * While performing tests, this method prints a header that reaches completion once all tests
+ * have been run.
+ *
+ * @param tests A set of tests to run.
+ * @param transformer A procedure for transforming the tests.
+ * @param configurator A procedure for configuring the tests.
+ * @param level The level of testing.
+ * @throws IOException If initial file configuration fails
+ */
+ private void validateAndRunWithRustRti(
+ // Set tests, Transformer transformer, Configurator configurator, TestLevel level, Path rustRtiProjectPath)
+ Set tests, Transformer transformer, Configurator configurator, TestLevel level)
+ throws IOException {
+ var done = 1;
+
+ System.out.println(THICK_LINE);
+
+ for (var test : tests) {
+ System.out.println(
+ "Running: " + test.toString() + " (" + (int) (done / (float) tests.size() * 100) + "%)");
+ try {
+ test.redirectOutputs();
+ prepare(test, transformer, configurator);
+ validate(test);
+ // generateCodeForRustRti(test, rustRtiProjectPath);
+ generateCodeForRustRti(test);
+ if (level == TestLevel.EXECUTION) {
+ execute(test);
+ }
+ test.markPassed();
+ } catch (TestError e) {
+ test.handleTestError(e);
+ } catch (Throwable e) {
+ test.handleTestError(
+ new TestError("Unknown exception during test execution", Result.TEST_EXCEPTION, e));
+ } finally {
+ test.restoreOutputs();
+ }
+ done++;
+ }
+
+ System.out.print(System.lineSeparator());
+ }
}