diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e60cd1d2d..2600d4c737 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,7 +100,7 @@ jobs: # Run the TypeScript integration tests. ts-tests: - uses: lf-lang/lingua-franca/.github/workflows/ts-tests.yml@master + uses: lf-lang/lingua-franca/.github/workflows/ts-tests.yml@ts-use-published-runtime needs: cancel # Run the serialization tests diff --git a/.github/workflows/ts-tests.yml b/.github/workflows/ts-tests.yml index 91f6153077..ddb1848093 100644 --- a/.github/workflows/ts-tests.yml +++ b/.github/workflows/ts-tests.yml @@ -2,27 +2,17 @@ name: TypeScript tests on: workflow_call: - inputs: - compiler-ref: - required: false - type: string - runtime-ref: - required: false - type: string jobs: run: strategy: matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] + platform: [ubuntu-latest, macos-latest, windows] runs-on: ${{ matrix.platform }} steps: - name: Check out lingua-franca repository uses: actions/checkout@v3 with: - repository: lf-lang/lingua-franca - submodules: true - ref: ${{ inputs.compiler-ref }} fetch-depth: 0 - name: Prepare build environment uses: ./.github/actions/prepare-build-env @@ -32,22 +22,10 @@ jobs: node-version: 18 - name: Install pnpm run: npm i -g pnpm - - name: Cache .pnpm-store - uses: actions/cache@v2 - with: - path: ~/.pnpm-store - key: ${{ runner.os }}-node${{ matrix.node-version }}-${{ hashFiles('org.lflang/src/lib/ts/package.json') }} - - name: Install Dependencies OS X + - name: Install coreutils (macOS) run: | brew install coreutils if: ${{ runner.os == 'macOS' }} - - name: Check out specific ref of reactor-ts - uses: actions/checkout@v2 - with: - repository: lf-lang/reactor-ts - path: org.lflang/src/lib/ts/reactor-ts - ref: ${{ inputs.runtime-ref }} - if: ${{ inputs.runtime-ref }} - name: Perform TypeScript tests run: | ./gradlew test --tests org.lflang.tests.runtime.TypeScriptTest.* @@ -57,4 +35,3 @@ jobs: file: org.lflang.tests/build/reports/xml/jacoco fail_ci_if_error: false verbose: true - if: ${{ !inputs.compiler-ref }} # i.e., if this is part of the main repo's CI diff --git a/.gitmodules b/.gitmodules index 2c32a4f671..d5763bfbc0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "org.lflang/src/lib/ts/reactor-ts"] - path = org.lflang/src/lib/ts/reactor-ts - url = https://github.com/lf-lang/reactor-ts.git [submodule "org.lflang/src/lib/c/reactor-c"] path = org.lflang/src/lib/c/reactor-c url = https://github.com/lf-lang/reactor-c.git diff --git a/org.lflang.cli/src/org/lflang/cli/Lfc.java b/org.lflang.cli/src/org/lflang/cli/Lfc.java index 00c5a14453..4487954aab 100644 --- a/org.lflang.cli/src/org/lflang/cli/Lfc.java +++ b/org.lflang.cli/src/org/lflang/cli/Lfc.java @@ -30,6 +30,7 @@ import org.lflang.LFStandaloneSetup; import org.lflang.LocalStrings; import org.lflang.generator.LFGeneratorContext; +import org.lflang.generator.LFGeneratorContext.BuildParm; import org.lflang.generator.MainContext; import com.google.inject.Inject; @@ -65,22 +66,22 @@ public class Lfc extends CliBase { * @author Marten Lohstroh */ enum CLIOption { - CLEAN("c", "clean", false, false, "Clean before building.", true), - COMPILER(null, "target-compiler", true, false, "Target compiler to invoke.", true), - EXTERNAL_RUNTIME_PATH(null, "external-runtime-path", true, false, "Specify an external runtime library to be used by the compiled binary.", true), - FEDERATED("f", "federated", false, false, "Treat main reactor as federated.", false), - HELP("h", "help", false, false, "Display this information.", true), - LOGGING(null, "logging", true, false, "The logging level to use by the generated binary", true), - LINT("l", "lint", false, false, "Enable or disable linting of generated code.", true), - NO_COMPILE("n", "no-compile", false, false, "Do not invoke target compiler.", true), - OUTPUT_PATH("o", "output-path", true, false, "Specify the root output directory.", false), - QUIET("q", "quiet", false, false, "Suppress output of the target compiler and other commands", true), - RTI("r", "rti", true, false, "Specify the location of the RTI.", true), - RUNTIME_VERSION(null, "runtime-version", true, false, "Specify the version of the runtime library used for compiling LF programs.", true), - SCHEDULER("s", "scheduler", true, false, "Specify the runtime scheduler (if supported).", true), - THREADING("t", "threading", true, false, "Specify whether the runtime should use multi-threading (true/false).", true), - VERSION(null, "version", false, false, "Print version information.", false), - WORKERS("w", "workers", true, false, "Specify the default number of worker threads.", true); + CLEAN(BuildParm.CLEAN, "c", false, false, true), + TARGET_COMPILER(BuildParm.TARGET_COMPILER, null, true, false, true), + EXTERNAL_RUNTIME_PATH(BuildParm.EXTERNAL_RUNTIME_PATH, null, true, false, true), + FEDERATED(BuildParm.FEDERATED, "f", false, false, false), + HELP(BuildParm.HELP, "h", false, false, true), + LOGGING(BuildParm.LOGGING, null, true, false, true), + LINT(BuildParm.LINT, "l",false, false, true), + NO_COMPILE(BuildParm.NO_COMPILE, "n", false, false, true), + OUTPUT_PATH(BuildParm.OUTPUT_PATH, "o", true, false, false), + QUIET(BuildParm.QUIET, "q", false, false, true), + RTI(BuildParm.RTI, "r", true, false, true), + RUNTIME_VERSION(BuildParm.RUNTIME_VERSION, null, true, false, true), + SCHEDULER(BuildParm.SCHEDULER, "s", true, false, true), + THREADING(BuildParm.THREADING, "t", true, false, true), + VERSION(BuildParm.VERSION, "version", false, false, false), + WORKERS(BuildParm.WORKERS, "w", true, false, true); /** * The corresponding Apache CLI Option object. @@ -95,20 +96,18 @@ enum CLIOption { /** * Construct a new CLIOption. * - * @param opt The short option name. E.g.: "f" denotes a flag - * "-f". - * @param longOpt The long option name. E.g.: "foo" denotes a flag - * "--foo". - * @param hasArg Whether or not this option has an argument. E.g.: + * @param parameter The build parameter that this CLI parameter corresponds to. + * @param shorthand The single-character switch to use for this option. E.g.: + * "-c" for "--clean". + * @param hasArg Whether this option has an argument. E.g.: * "--foo bar" where "bar" is the argument value. - * @param isReq Whether or not this option is required. If it is + * @param isReq Whether this option is required. If it is * required but not specified a menu is shown. - * @param description The description used in the menu. - * @param passOn Whether or not to pass this option as a property + * @param passOn Whether to pass this option as a property * to the code generator. */ - CLIOption(String opt, String longOpt, boolean hasArg, boolean isReq, String description, boolean passOn) { - this.option = new Option(opt, longOpt, hasArg, description); + CLIOption(BuildParm parameter, String shorthand, boolean hasArg, boolean isReq, boolean passOn) { + this.option = new Option(shorthand, parameter.getKey(), hasArg, parameter.description); option.setRequired(isReq); this.passOn = passOn; } diff --git a/org.lflang.tests/build.gradle b/org.lflang.tests/build.gradle index 3f223ec5f5..580337f2b6 100644 --- a/org.lflang.tests/build.gradle +++ b/org.lflang.tests/build.gradle @@ -47,6 +47,7 @@ test { } // Pass the scheduler property on to the Java VM systemProperty 'scheduler', System.getProperty('scheduler') + systemProperty 'runtime', System.getProperty('runtime') // Suggested by Gradle documentation: https://guides.gradle.org/performance/#parallel_test_execution maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 useJUnitPlatform() diff --git a/org.lflang.tests/src/org/lflang/tests/TestBase.java b/org.lflang.tests/src/org/lflang/tests/TestBase.java index 2121317b54..29ba4f7bb7 100644 --- a/org.lflang.tests/src/org/lflang/tests/TestBase.java +++ b/org.lflang.tests/src/org/lflang/tests/TestBase.java @@ -47,6 +47,7 @@ import org.lflang.generator.DockerGeneratorBase; import org.lflang.generator.LFGenerator; import org.lflang.generator.LFGeneratorContext; +import org.lflang.generator.LFGeneratorContext.BuildParm; import org.lflang.generator.MainContext; import org.lflang.tests.Configurators.Configurator; import org.lflang.tests.LFTest.Result; @@ -384,8 +385,21 @@ private static void checkAndReportFailures(Set tests) { * @throws IOException if there is any file access problem */ private LFGeneratorContext configure(LFTest test, Configurator configurator, TestLevel level) throws IOException { + var props = new Properties(); + var sysProps = System.getProperties(); + // Set the external-runtime-path property if it was specified. + if (sysProps.containsKey("runtime")) { + var rt = sysProps.get("runtime").toString(); + if (!rt.isEmpty()) { + props.setProperty(BuildParm.EXTERNAL_RUNTIME_PATH.getKey(), rt); + System.out.println("Using runtime: " + sysProps.get("runtime").toString()); + } + } else { + System.out.println("Using default runtime."); + } + var context = new MainContext( - LFGeneratorContext.Mode.STANDALONE, CancelIndicator.NullImpl, (m, p) -> {}, new Properties(), true, + LFGeneratorContext.Mode.STANDALONE, CancelIndicator.NullImpl, (m, p) -> {}, props, true, fileConfig -> new DefaultErrorReporter() ); diff --git a/org.lflang/src/lib/ts/package.json b/org.lflang/src/lib/ts/package.json index 75897da3e7..8f7cb491ca 100644 --- a/org.lflang/src/lib/ts/package.json +++ b/org.lflang/src/lib/ts/package.json @@ -1,16 +1,8 @@ { "name": "LinguaFrancaDefault", - "version": "0.0.1", - "description": "A default Lingua Franca project for the TypeScript target", "type": "commonjs", - "repository": { - "type": "git", - "url": "https://github.com/icyphy/lingua-franca" - }, - "license": "BSD-2-Clause", "dependencies": { - "reactor-ts": "file:./reactor-ts", - "@types/reactor-ts": "file:./reactor-ts", + "@lf-lang/reactor-ts": "^0.1.0", "@babel/cli": "^7.8.4", "@babel/core": "^7.8.7", "@babel/node": "^7.8.7", diff --git a/org.lflang/src/lib/ts/reactor-ts b/org.lflang/src/lib/ts/reactor-ts deleted file mode 160000 index aeb813a403..0000000000 --- a/org.lflang/src/lib/ts/reactor-ts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit aeb813a403b42780ba5af63c5afd575954238865 diff --git a/org.lflang/src/lib/ts/tsconfig.json b/org.lflang/src/lib/ts/tsconfig.json index 104b89a4c2..b771b75281 100644 --- a/org.lflang/src/lib/ts/tsconfig.json +++ b/org.lflang/src/lib/ts/tsconfig.json @@ -3,7 +3,7 @@ "allowJs": true, "noEmit": true, "target": "esnext", - "types": ["node", "reactor-ts", "ulog", "microtime", "command-line-args", "command-line-usage"], + "types": ["node", "@lf-lang/reactor-ts", "ulog", "microtime", "command-line-args", "command-line-usage"], "esModuleInterop": true, "isolatedModules": true, "lib": ["esnext", "dom"], @@ -13,7 +13,7 @@ "strictBindCallApply": true, "strictNullChecks": true, "strictFunctionTypes": true, - "typeRoots": ["./node_modules/@types/", "./node_modules/reactor-ts/src/core/@types/"] + "typeRoots": ["./node_modules/@types/", "./node_modules/@lf-lang/reactor-ts/src/core/@types/"] }, "include": [ "src/**/*" diff --git a/org.lflang/src/org/lflang/generator/LFGeneratorContext.java b/org.lflang/src/org/lflang/generator/LFGeneratorContext.java index e5d39d5fb8..3fd521dc17 100644 --- a/org.lflang/src/org/lflang/generator/LFGeneratorContext.java +++ b/org.lflang/src/org/lflang/generator/LFGeneratorContext.java @@ -21,6 +21,50 @@ */ public interface LFGeneratorContext extends IGeneratorContext { + /** + * Enumeration of keys used to parameterize the build process. + */ + public enum BuildParm { + CLEAN("Clean before building."), + EXTERNAL_RUNTIME_PATH("Specify an external runtime library to be used by the compiled binary."), + FEDERATED("Treat main reactor as federated."), + HELP("Display this information."), + LOGGING("The logging level to use by the generated binary"), + LINT("Enable or disable linting of generated code."), + NO_COMPILE("Do not invoke target compiler."), + OUTPUT_PATH("Specify the root output directory."), + QUIET("Suppress output of the target compiler and other commands"), + RTI("Specify the location of the RTI."), + RUNTIME_VERSION("Specify the version of the runtime library used for compiling LF programs."), + SCHEDULER("Specify the runtime scheduler (if supported)."), + TARGET_COMPILER("Target compiler to invoke."), + THREADING("Specify whether the runtime should use multi-threading (true/false)."), + VERSION("Print version information."), + WORKERS("Specify the default number of worker threads."); + + public final String description; + + BuildParm(String description) { + this.description = description; + } + + /** + * Return the string to use as the key to store a value relating to this parameter. + */ + public String getKey() { + return this.name().toLowerCase().replace('_', '-'); + } + + /** + * Return the value corresponding to this parameter or `null` if there is none. + * @param context The context passed to the code generator. + */ + public String getValue(LFGeneratorContext context) { + return context.getArgs().getProperty(this.getKey()); + } + } + + enum Mode { STANDALONE, EPOCH, diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index 58eb3cef79..82cd08c616 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -91,8 +91,6 @@ class TSGenerator( */ val CONFIG_FILES = arrayOf("package.json", "tsconfig.json", "babel.config.js", ".eslintrc.json") - val RT_CONFIG_FILES = arrayOf("package.json", "package-lock.json", "tsconfig.json", ".babelrc") - private val VG = ExpressionGenerator(::timeInTargetLanguage) { param -> "this.${param.name}.get()" } @@ -147,9 +145,8 @@ class TSGenerator( createMainReactorInstance() clean(context) - copyRuntime() - collectDependencies(resource, context, tsFileConfig.reactorTsPath(), true) copyConfigFiles() + updatePackageConfig(context) val codeMaps = HashMap() val dockerGenerator = TSDockerGenerator(isFederated) @@ -193,6 +190,30 @@ class TSGenerator( } } + /** + * Update package.json according to given build parameters. + */ + private fun updatePackageConfig(context: LFGeneratorContext) { + var rtPath = LFGeneratorContext.BuildParm.EXTERNAL_RUNTIME_PATH.getValue(context) + val rtVersion = LFGeneratorContext.BuildParm.RUNTIME_VERSION.getValue(context) + val sb = StringBuffer(""); + val manifest = fileConfig.srcGenPath.resolve("package.json"); + val rtRegex = Regex("(\"@lf-lang/reactor-ts\")(.+)") + if (rtPath != null && !rtPath.startsWith("file:")) rtPath = "file:$rtPath" + // FIXME: do better CLI arg validation upstream + // https://github.com/lf-lang/lingua-franca/issues/1429 + manifest.toFile().forEachLine { + var line = it.replace("\"LinguaFrancaDefault\"", "\"${fileConfig.name}\""); + if (rtPath != null) { + line = line.replace(rtRegex, "$1: \"$rtPath\",") + } else if (rtVersion != null) { + line = line.replace(rtRegex, "$1: \"git://github.com/lf-lang/reactor-ts.git#$rtVersion\",") + } + sb.appendLine(line) + } + manifest.toFile().writeText(sb.toString()); + } + /** * Clean up the src-gen directory as needed to prepare for code generation. */ @@ -203,23 +224,6 @@ class TSGenerator( ) } - /** - * Copy the TypeScript runtime so that it is accessible to the generated code. - */ - private fun copyRuntime() { - FileUtil.copyDirectoryFromClassPath( - "$LIB_PATH/reactor-ts/src/core", - tsFileConfig.reactorTsPath().resolve("src").resolve("core"), - true - ) - for (configFile in RT_CONFIG_FILES) { - FileUtil.copyFileFromClassPath( - "$LIB_PATH/reactor-ts/$configFile", - tsFileConfig.reactorTsPath().resolve(configFile) - ) - } - } - /** * For each configuration file that is not present in the same directory * as the source file, copy a default version from $LIB_PATH/. diff --git a/org.lflang/src/org/lflang/generator/ts/TSImportPreambleGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSImportPreambleGenerator.kt index 22ec208f42..e623d2e05d 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSImportPreambleGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSImportPreambleGenerator.kt @@ -51,18 +51,18 @@ class TSImportPreambleGenerator( const val DEFAULT_IMPORTS = """ |import commandLineArgs from 'command-line-args' |import commandLineUsage from 'command-line-usage' - |import {Parameter as __Parameter, Timer as __Timer, Reactor as __Reactor, App as __App} from 'reactor-ts' - |import {Action as __Action, Startup as __Startup, FederatePortAction as __FederatePortAction} from 'reactor-ts' - |import {Bank as __Bank} from 'reactor-ts' - |import {FederatedApp as __FederatedApp} from 'reactor-ts' - |import {InPort as __InPort, OutPort as __OutPort, Port as __Port, WritablePort as __WritablePort, WritableMultiPort as __WritableMultiPort} from 'reactor-ts' - |import {InMultiPort as __InMultiPort, OutMultiPort as __OutMultiPort} from 'reactor-ts' - |import {Reaction as __Reaction} from 'reactor-ts' - |import {State as __State} from 'reactor-ts' - |import {TimeUnit, TimeValue, Tag as __Tag, Origin as __Origin} from 'reactor-ts' - |import {Args as __Args, Variable as __Variable, Triggers as __Triggers, Present, Read, Write, ReadWrite, MultiReadWrite, Sched} from 'reactor-ts' - |import {Log} from 'reactor-ts' - |import {ProcessedCommandLineArgs as __ProcessedCommandLineArgs, CommandLineOptionDefs as __CommandLineOptionDefs, CommandLineUsageDefs as __CommandLineUsageDefs, CommandLineOptionSpec as __CommandLineOptionSpec, unitBasedTimeValueCLAType as __unitBasedTimeValueCLAType, booleanCLAType as __booleanCLAType} from 'reactor-ts' + |import {Parameter as __Parameter, Timer as __Timer, Reactor as __Reactor, App as __App} from '@lf-lang/reactor-ts' + |import {Action as __Action, Startup as __Startup, FederatePortAction as __FederatePortAction} from '@lf-lang/reactor-ts' + |import {Bank as __Bank} from '@lf-lang/reactor-ts' + |import {FederatedApp as __FederatedApp} from '@lf-lang/reactor-ts' + |import {InPort as __InPort, OutPort as __OutPort, Port as __Port, WritablePort as __WritablePort, WritableMultiPort as __WritableMultiPort} from '@lf-lang/reactor-ts' + |import {InMultiPort as __InMultiPort, OutMultiPort as __OutMultiPort} from '@lf-lang/reactor-ts' + |import {Reaction as __Reaction} from '@lf-lang/reactor-ts' + |import {State as __State} from '@lf-lang/reactor-ts' + |import {TimeUnit, TimeValue, Tag as __Tag, Origin as __Origin} from '@lf-lang/reactor-ts' + |import {Args as __Args, Variable as __Variable, Triggers as __Triggers, Present, Read, Write, ReadWrite, MultiReadWrite, Sched} from '@lf-lang/reactor-ts' + |import {Log} from '@lf-lang/reactor-ts' + |import {ProcessedCommandLineArgs as __ProcessedCommandLineArgs, CommandLineOptionDefs as __CommandLineOptionDefs, CommandLineUsageDefs as __CommandLineUsageDefs, CommandLineOptionSpec as __CommandLineOptionSpec, unitBasedTimeValueCLAType as __unitBasedTimeValueCLAType, booleanCLAType as __booleanCLAType} from '@lf-lang/reactor-ts' |""" } @@ -102,4 +102,4 @@ class TSImportPreambleGenerator( fun generatePreamble(): String { return generateDefaultImports() + generateProtoPreamble() } -} \ No newline at end of file +} diff --git a/test/TypeScript/README.md b/test/TypeScript/README.md index 1f61876820..c2e04e2efd 100644 --- a/test/TypeScript/README.md +++ b/test/TypeScript/README.md @@ -1,2 +1,19 @@ # Library of TypeScript tests To run the entire test suite, execute `./gradlew test --tests org.lflang.tests.runtime.TypeScriptTest.*`. + +## Using an alternative runtime +To run the tests with an alternative runtime, use the `-Druntime` flag to specify where to find it. + +### Examples +- To use a local checkout of `reactor-ts` located in the local file system in the directory `~/lf-lang/reactor-ts`: +``` +./gradlew test --tests org.lflang.tests.runtime.TypeScriptTest.* -Druntime="~/lf-lang/reactor-ts" +``` +- Note that `lfc` can be pointed to an alternative runtime as well, using the `external-runtime-path` switch: +``` +lfc test/TypeScript/src/Minimal.lf --external-runtime-path ~/lf-lang/reactor-ts +``` +- To point `lfc` to a particular ref (e.g. `main`, `v0.1.0` or `f8c6d2379f278e22ad48410bf06cf0909405ecc3`) in the `lf-lang/reactor-ts` repo: +``` +lfc test/TypeScript/src/Minimal.lf --runtime-version +``` \ No newline at end of file