Browse Source

Merge pull request #84 from dora-rs/cli

Start creating command line interface with `check`, `build`, `graph` and `templates` commands
tags/v0.0.0-test-pr-120
Philipp Oppermann GitHub 3 years ago
parent
commit
2a9495cb68
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1212 additions and 57 deletions
  1. +344
    -24
      Cargo.lock
  2. +18
    -0
      binaries/cli/Cargo.toml
  3. +73
    -0
      binaries/cli/src/build.rs
  4. +166
    -0
      binaries/cli/src/check.rs
  5. +17
    -0
      binaries/cli/src/graph/mermaid-template.html
  6. +28
    -0
      binaries/cli/src/graph/mod.rs
  7. +127
    -0
      binaries/cli/src/main.rs
  8. +85
    -0
      binaries/cli/src/template/c/mod.rs
  9. +49
    -0
      binaries/cli/src/template/c/node/node-template.c
  10. +60
    -0
      binaries/cli/src/template/c/operator/operator-template.c
  11. +1
    -0
      binaries/cli/src/template/cxx.rs
  12. +13
    -0
      binaries/cli/src/template/mod.rs
  13. +23
    -0
      binaries/cli/src/template/python/mod.rs
  14. +91
    -0
      binaries/cli/src/template/rust/mod.rs
  15. +9
    -0
      binaries/cli/src/template/rust/node/Cargo-template.toml
  16. +15
    -0
      binaries/cli/src/template/rust/node/main-template.rs
  17. +12
    -0
      binaries/cli/src/template/rust/operator/Cargo-template.toml
  18. +23
    -0
      binaries/cli/src/template/rust/operator/lib-template.rs
  19. +0
    -13
      binaries/coordinator/src/lib.rs
  20. +2
    -12
      binaries/runtime/src/operator/shared_lib.rs
  21. +3
    -0
      examples/rust-dataflow/dataflow.yml
  22. +15
    -4
      examples/rust-dataflow/run.rs
  23. +1
    -0
      libraries/core/Cargo.toml
  24. +11
    -4
      libraries/core/src/descriptor/mod.rs
  25. +26
    -0
      libraries/core/src/lib.rs

+ 344
- 24
Cargo.lock View File

@@ -389,6 +389,12 @@ version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"

[[package]]
name = "cesu8"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"

[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -427,26 +433,54 @@ dependencies = [

[[package]]
name = "clap"
version = "3.1.12"
version = "3.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c167e37342afc5f33fd87bbc870cedd020d2a6dffa05d45ccd9241fbdd146db"
checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"clap_derive 3.2.18",
"clap_lex 0.2.4",
"indexmap",
"lazy_static",
"once_cell",
"strsim 0.10.0",
"termcolor",
"textwrap 0.15.0",
]

[[package]]
name = "clap"
version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3943ff31339d6d148b4e9cfb9b1eb0f814989ed21dfede3c2d2e11f3d9497f60"
dependencies = [
"atty",
"bitflags",
"clap_derive 4.0.1",
"clap_lex 0.3.0",
"once_cell",
"strsim 0.10.0",
"termcolor",
]

[[package]]
name = "clap_derive"
version = "3.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65"
dependencies = [
"heck 0.4.0",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]

[[package]]
name = "clap_derive"
version = "3.1.7"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1"
checksum = "ca689d7434ce44517a12a89456b2be4d1ea1cafcd8f581978c03d45f5a5c12a7"
dependencies = [
"heck 0.4.0",
"proc-macro-error",
@@ -457,9 +491,18 @@ dependencies = [

[[package]]
name = "clap_lex"
version = "0.1.1"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]

[[package]]
name = "clap_lex"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "189ddd3b5d32a70b35e7686054371742a937b0d99128e76dde6340210e966669"
checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
dependencies = [
"os_str_bytes",
]
@@ -474,6 +517,16 @@ dependencies = [
"unicode-width",
]

[[package]]
name = "combine"
version = "4.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
dependencies = [
"bytes",
"memchr",
]

[[package]]
name = "communication-layer-pub-sub"
version = "0.1.0"
@@ -660,6 +713,12 @@ dependencies = [
"syn",
]

[[package]]
name = "cty"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"

[[package]]
name = "cxx"
version = "1.0.73"
@@ -728,6 +787,41 @@ dependencies = [
"syn",
]

[[package]]
name = "darling"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
dependencies = [
"darling_core",
"darling_macro",
]

[[package]]
name = "darling_core"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim 0.10.0",
"syn",
]

[[package]]
name = "darling_macro"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
dependencies = [
"darling_core",
"quote",
"syn",
]

[[package]]
name = "dashmap"
version = "4.0.2"
@@ -787,12 +881,24 @@ dependencies = [
"winapi",
]

[[package]]
name = "dora-cli"
version = "0.1.0"
dependencies = [
"clap 4.0.3",
"dora-core",
"eyre",
"serde_yaml 0.9.11",
"tempfile",
"webbrowser",
]

[[package]]
name = "dora-coordinator"
version = "0.1.0"
dependencies = [
"bincode",
"clap 3.1.12",
"clap 3.2.20",
"dora-core",
"dora-message",
"dora-node-api",
@@ -801,7 +907,7 @@ dependencies = [
"futures-concurrency",
"rand",
"serde",
"serde_yaml",
"serde_yaml 0.8.23",
"time",
"tokio",
"tokio-stream",
@@ -818,6 +924,7 @@ dependencies = [
"dora-node-api",
"eyre",
"serde",
"serde_yaml 0.9.11",
]

[[package]]
@@ -860,7 +967,7 @@ dependencies = [
"flume",
"once_cell",
"serde",
"serde_yaml",
"serde_yaml 0.8.23",
"thiserror",
"tokio",
"tracing",
@@ -886,7 +993,7 @@ dependencies = [
"eyre",
"flume",
"pyo3",
"serde_yaml",
"serde_yaml 0.8.23",
]

[[package]]
@@ -926,7 +1033,7 @@ dependencies = [
name = "dora-runtime"
version = "0.1.0"
dependencies = [
"clap 3.1.12",
"clap 3.2.20",
"dora-core",
"dora-message",
"dora-node-api",
@@ -938,7 +1045,7 @@ dependencies = [
"futures-concurrency",
"libloading",
"pyo3",
"serde_yaml",
"serde_yaml 0.8.23",
"tokio",
"tokio-stream",
"tracing",
@@ -1294,9 +1401,9 @@ dependencies = [

[[package]]
name = "hashbrown"
version = "0.11.2"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"

[[package]]
name = "heck"
@@ -1468,6 +1575,12 @@ dependencies = [
"thiserror",
]

[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"

[[package]]
name = "idna"
version = "0.2.3"
@@ -1487,9 +1600,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"

[[package]]
name = "indexmap"
version = "1.8.0"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [
"autocfg 1.1.0",
"hashbrown",
@@ -1565,6 +1678,26 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"

[[package]]
name = "jni"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec"
dependencies = [
"cesu8",
"combine",
"jni-sys",
"log",
"thiserror",
"walkdir",
]

[[package]]
name = "jni-sys"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"

[[package]]
name = "js-sys"
version = "0.3.56"
@@ -1681,6 +1814,15 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26a8d2502d5aa4d411ef494ba7470eb299f05725179ce3b5de77aa01a9ffdea"

[[package]]
name = "malloc_buf"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
dependencies = [
"libc",
]

[[package]]
name = "maplit"
version = "1.0.2"
@@ -1858,6 +2000,64 @@ name = "napi-sys"
version = "1.0.0"
source = "git+https://github.com/getditto/napi-rs?branch=ditto/closure-into-jsfunction#da095cc3f1af133344083b525d7e9763b347e249"

[[package]]
name = "ndk"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0"
dependencies = [
"bitflags",
"jni-sys",
"ndk-sys",
"num_enum",
"raw-window-handle",
"thiserror",
]

[[package]]
name = "ndk-context"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"

[[package]]
name = "ndk-glue"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0434fabdd2c15e0aab768ca31d5b7b333717f03cf02037d5a0a3ff3c278ed67f"
dependencies = [
"libc",
"log",
"ndk",
"ndk-context",
"ndk-macro",
"ndk-sys",
"once_cell",
"parking_lot",
]

[[package]]
name = "ndk-macro"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c"
dependencies = [
"darling",
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]

[[package]]
name = "ndk-sys"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21d83ec9c63ec5bf950200a8e508bdad6659972187b625469f58ef8c08e29046"
dependencies = [
"jni-sys",
]

[[package]]
name = "nix"
version = "0.22.3"
@@ -1952,6 +2152,27 @@ dependencies = [
"libc",
]

[[package]]
name = "num_enum"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
dependencies = [
"num_enum_derive",
]

[[package]]
name = "num_enum_derive"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]

[[package]]
name = "num_threads"
version = "0.1.5"
@@ -1961,6 +2182,15 @@ dependencies = [
"libc",
]

[[package]]
name = "objc"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [
"malloc_buf",
]

[[package]]
name = "once_cell"
version = "1.13.0"
@@ -2361,6 +2591,17 @@ dependencies = [
"syn",
]

[[package]]
name = "proc-macro-crate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9"
dependencies = [
"once_cell",
"thiserror",
"toml",
]

[[package]]
name = "proc-macro-error"
version = "1.0.4"
@@ -2605,6 +2846,15 @@ dependencies = [
"getrandom",
]

[[package]]
name = "raw-window-handle"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a"
dependencies = [
"cty",
]

[[package]]
name = "rayon"
version = "1.5.1"
@@ -2864,6 +3114,15 @@ dependencies = [
"syn",
]

[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]

[[package]]
name = "schannel"
version = "0.1.19"
@@ -2937,18 +3196,18 @@ checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4"

[[package]]
name = "serde"
version = "1.0.136"
version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
dependencies = [
"serde_derive",
]

[[package]]
name = "serde_derive"
version = "1.0.136"
version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
dependencies = [
"proc-macro2",
"quote",
@@ -2978,6 +3237,19 @@ dependencies = [
"yaml-rust",
]

[[package]]
name = "serde_yaml"
version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89f31df3f50926cdf2855da5fd8812295c34752cb20438dae42a67f79e021ac3"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]

[[package]]
name = "sha-1"
version = "0.8.2"
@@ -3367,6 +3639,15 @@ dependencies = [
"tracing",
]

[[package]]
name = "toml"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]

[[package]]
name = "tonic"
version = "0.6.2"
@@ -3588,6 +3869,12 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "514672a55d7380da379785a4d70ca8386c8883ff7eaae877be4d2081cebe73d8"

[[package]]
name = "unsafe-libyaml"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "931179334a56395bcf64ba5e0ff56781381c1a5832178280c7d7f91d1679aeb0"

[[package]]
name = "untrusted"
version = "0.7.1"
@@ -3716,6 +4003,17 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"

[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]

[[package]]
name = "want"
version = "0.3.0"
@@ -3816,6 +4114,22 @@ dependencies = [
"wasm-bindgen",
]

[[package]]
name = "webbrowser"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d62aa75495ab67cdc273d0b95cc76bcedfea2ba28338a4cf9b4137949dfac5"
dependencies = [
"jni",
"ndk-glue",
"objc",
"raw-window-handle",
"url",
"web-sys",
"widestring",
"winapi",
]

[[package]]
name = "webpki"
version = "0.21.4"
@@ -3865,6 +4179,12 @@ dependencies = [
"libc",
]

[[package]]
name = "widestring"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8"

[[package]]
name = "winapi"
version = "0.3.9"
@@ -4059,7 +4379,7 @@ dependencies = [
"num_cpus",
"serde",
"serde_json",
"serde_yaml",
"serde_yaml 0.8.23",
"validated_struct",
"zenoh-cfg-properties",
"zenoh-core",


+ 18
- 0
binaries/cli/Cargo.toml View File

@@ -0,0 +1,18 @@
[package]
name = "dora-cli"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[[bin]]
name = "dora"
path = "src/main.rs"

[dependencies]
clap = { version = "4.0.3", features = ["derive"] }
eyre = "0.6.8"
dora-core = { path = "../../libraries/core" }
serde_yaml = "0.9.11"
tempfile = "3.3.0"
webbrowser = "0.8.0"

+ 73
- 0
binaries/cli/src/build.rs View File

@@ -0,0 +1,73 @@
use crate::graph;
use dora_core::descriptor::{OperatorId, SINGLE_OPERATOR_DEFAULT_ID};
use eyre::{eyre, Context};
use std::{path::Path, process::Command};

pub fn build(dataflow: &Path) -> eyre::Result<()> {
let descriptor = graph::read_descriptor(dataflow)?;
let dataflow_absolute = if dataflow.is_relative() {
std::env::current_dir().unwrap().join(dataflow)
} else {
dataflow.to_owned()
};
let working_dir = dataflow_absolute.parent().unwrap();

let default_op_id = OperatorId::from(SINGLE_OPERATOR_DEFAULT_ID.to_string());

for node in &descriptor.nodes {
match &node.kind {
dora_core::descriptor::NodeKind::Runtime(runtime_node) => {
for operator in &runtime_node.operators {
run_build_command(operator.config.build.as_deref(), &working_dir)
.with_context(|| {
format!(
"build command failed for operator `{}/{}`",
node.id, operator.id
)
})?;
}
}
dora_core::descriptor::NodeKind::Custom(custom_node) => {
run_build_command(custom_node.build.as_deref(), &working_dir).with_context(
|| format!("build command failed for custom node `{}`", node.id),
)?
}
dora_core::descriptor::NodeKind::Operator(operator) => {
run_build_command(operator.config.build.as_deref(), &working_dir).with_context(
|| {
format!(
"build command failed for operator `{}/{}`",
node.id,
operator.id.as_ref().unwrap_or(&default_op_id)
)
},
)?
}
}
}

Ok(())
}

fn run_build_command(build: Option<&str>, working_dir: &Path) -> eyre::Result<()> {
if let Some(build) = build {
let mut split = build.split_whitespace();
let mut cmd = Command::new(
split
.next()
.ok_or_else(|| eyre!("build command is empty"))?,
);
cmd.args(split);
cmd.current_dir(working_dir);
let exit_status = cmd
.status()
.wrap_err_with(|| format!("failed to run `{}`", build))?;
if exit_status.success() {
Ok(())
} else {
Err(eyre!("build command returned an error code"))
}
} else {
Ok(())
}
}

+ 166
- 0
binaries/cli/src/check.rs View File

@@ -0,0 +1,166 @@
use crate::graph::read_descriptor;
use dora_core::{
adjust_shared_library_path,
descriptor::{self, CoreNodeKind, InputMapping, OperatorSource, UserInputMapping},
};
use eyre::{bail, eyre, Context};
use std::{env::consts::EXE_EXTENSION, path::Path};

pub fn check(dataflow_path: &Path, runtime: &Path) -> eyre::Result<()> {
let runtime = runtime.with_extension(EXE_EXTENSION);
let descriptor = read_descriptor(&dataflow_path).wrap_err_with(|| {
format!(
"failed to read dataflow descriptor at {}",
dataflow_path.display()
)
})?;
let base = dataflow_path
.canonicalize()
.unwrap()
.parent()
.unwrap()
.to_owned();

let nodes = descriptor.resolve_aliases();

if nodes
.iter()
.any(|n| matches!(n.kind, CoreNodeKind::Runtime(_)))
&& !runtime.is_file()
{
bail!(
"There is no runtime at {}, or it is not a file",
runtime.display()
);
}

// check that nodes and operators exist
for node in &nodes {
match &node.kind {
descriptor::CoreNodeKind::Custom(node) => {
let mut args = node.run.split_ascii_whitespace();
let raw = Path::new(
args.next()
.ok_or_else(|| eyre!("`run` field must not be empty"))?,
);
let path = if raw.extension().is_none() {
raw.with_extension(EXE_EXTENSION)
} else {
raw.to_owned()
};
base.join(&path)
.canonicalize()
.wrap_err_with(|| format!("no node exists at `{}`", path.display()))?;
}
descriptor::CoreNodeKind::Runtime(node) => {
for operator_definition in &node.operators {
match &operator_definition.config.source {
OperatorSource::SharedLibrary(path) => {
let path = adjust_shared_library_path(path)?;

if !base.join(&path).exists() {
bail!("no shared library at `{}`", path.display());
}
}
OperatorSource::Python(path) => {
if !base.join(&path).exists() {
bail!("no Python library at `{}`", path.display());
}
}
OperatorSource::Wasm(path) => {
if !base.join(&path).exists() {
bail!("no WASM library at `{}`", path.display());
}
}
}
}
}
}
}

// check that all inputs mappings point to an existing output
for node in &nodes {
match &node.kind {
descriptor::CoreNodeKind::Custom(custom_node) => {
for (input_id, mapping) in &custom_node.run_config.inputs {
check_input(mapping, &nodes, &format!("{}/{input_id}", node.id))?;
}
}
descriptor::CoreNodeKind::Runtime(runtime_node) => {
for operator_definition in &runtime_node.operators {
for (input_id, mapping) in &operator_definition.config.inputs {
check_input(
mapping,
&nodes,
&format!("{}/{}/{input_id}", operator_definition.id, node.id),
)?;
}
}
}
};
}

Ok(())
}

fn check_input(
mapping: &InputMapping,
nodes: &[dora_core::descriptor::ResolvedNode],
input_id_str: &str,
) -> Result<(), eyre::ErrReport> {
Ok(match mapping {
InputMapping::Timer { interval: _ } => {}
InputMapping::User(UserInputMapping {
source,
operator,
output,
}) => {
let source_node = nodes.iter().find(|n| &n.id == source).ok_or_else(|| {
eyre!("source node `{source}` mapped to input `{input_id_str}` does not exist",)
})?;
if let Some(operator_id) = operator {
let operator = match &source_node.kind {
CoreNodeKind::Runtime(runtime) => {
let operator = runtime.operators.iter().find(|o| &o.id == operator_id);
operator.ok_or_else(|| {
eyre!(
"source operator `{source}/{operator_id}` used \
for input `{input_id_str}` does not exist",
)
})?
}
CoreNodeKind::Custom(_) => {
bail!(
"input `{input_id_str}` references operator \
`{source}/{operator_id}`, but `{source}` is a \
custom node",
);
}
};

if !operator.config.outputs.contains(output) {
bail!(
"output `{source}/{operator_id}/{output}` mapped to \
input `{input_id_str}` does not exist",
);
}
} else {
match &source_node.kind {
CoreNodeKind::Runtime(_) => bail!(
"input `{input_id_str}` references output \
`{source}/{output}`, but `{source}` is a \
runtime node",
),
CoreNodeKind::Custom(custom_node) => {
if !custom_node.run_config.outputs.contains(output) {
bail!(
"output `{source}/{output}` mapped to \
input `{input_id_str}` does not exist",
);
}
}
}
}
}
})
}

+ 17
- 0
binaries/cli/src/graph/mermaid-template.html View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8">
</head>

<body>
<div class="mermaid">
____insert____
</div>
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>mermaid.initialize({ startOnLoad: true, securityLevel: 'loose' });
</script>
</body>

</html>

+ 28
- 0
binaries/cli/src/graph/mod.rs View File

@@ -0,0 +1,28 @@
use std::{fs, path::Path};

use dora_core::descriptor::Descriptor;
use eyre::Context;

const MERMAID_TEMPLATE: &str = include_str!("mermaid-template.html");

pub fn visualize_as_html(dataflow: &Path) -> eyre::Result<String> {
let mermaid = visualize_as_mermaid(dataflow)?;
Ok(MERMAID_TEMPLATE.replacen("____insert____", &mermaid, 1))
}

pub fn visualize_as_mermaid(dataflow: &Path) -> eyre::Result<String> {
let descriptor = read_descriptor(&dataflow)
.with_context(|| format!("failed to read dataflow at `{}`", dataflow.display()))?;
let visualized = descriptor
.visualize_as_mermaid()
.context("failed to visualize descriptor")?;

Ok(visualized)
}

pub fn read_descriptor(file: &Path) -> eyre::Result<Descriptor> {
let descriptor_file = fs::read(file).context("failed to open given file")?;
let descriptor: Descriptor =
serde_yaml::from_slice(&descriptor_file).context("failed to parse given descriptor")?;
Ok(descriptor)
}

+ 127
- 0
binaries/cli/src/main.rs View File

@@ -0,0 +1,127 @@
use clap::Parser;
use eyre::Context;
use std::{io::Write, path::PathBuf};
use tempfile::NamedTempFile;

mod build;
mod check;
mod graph;
mod template;

#[derive(Debug, clap::Parser)]
#[clap(version)]
struct Args {
#[clap(subcommand)]
command: Command,
}

#[derive(Debug, clap::Subcommand)]
enum Command {
Check {
dataflow: PathBuf,
runtime_path: PathBuf,
},
Graph {
dataflow: PathBuf,
#[clap(long, action)]
mermaid: bool,
#[clap(long, action)]
open: bool,
},
Build {
dataflow: PathBuf,
},
New {
#[clap(flatten)]
args: CommandNew,
},
Dashboard,
Start,
Stop,
Logs,
Metrics,
Stats,
List,
Get,
Upgrade,
}

#[derive(Debug, clap::Args)]
pub struct CommandNew {
#[clap(long, value_enum, default_value_t = Kind::Operator)]
kind: Kind,
#[clap(long, value_enum, default_value_t = Lang::Rust)]
lang: Lang,
name: String,
path: Option<PathBuf>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
enum Kind {
Operator,
CustomNode,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
enum Lang {
Rust,
Python,
C,
Cxx,
}

fn main() -> eyre::Result<()> {
let args = Args::parse();

match args.command {
Command::Check {
dataflow,
runtime_path,
} => check::check(&dataflow, &runtime_path)?,
Command::Graph {
dataflow,
mermaid,
open,
} => {
if mermaid {
let visualized = graph::visualize_as_mermaid(&dataflow)?;
println!("{visualized}");
println!(
"Paste the above output on https://mermaid.live/ or in a \
```mermaid code block on GitHub to display it."
);
} else {
let html = graph::visualize_as_html(&dataflow)?;
let mut file = NamedTempFile::new().context("failed to create temp file")?;
file.as_file_mut().write_all(html.as_bytes())?;

let path = file.path().to_owned();
file.keep()?;

println!(
"View graph by opening the following in your browser:\n file://{}",
path.display()
);

if open {
webbrowser::open(path.as_os_str().to_str().unwrap())?;
}
}
}
Command::Build { dataflow } => {
build::build(&dataflow)?;
}
Command::New { args } => template::create(args)?,
Command::Dashboard => todo!(),
Command::Start => todo!(),
Command::Stop => todo!(),
Command::Logs => todo!(),
Command::Metrics => todo!(),
Command::Stats => todo!(),
Command::List => todo!(),
Command::Get => todo!(),
Command::Upgrade => todo!(),
}

Ok(())
}

+ 85
- 0
binaries/cli/src/template/c/mod.rs View File

@@ -0,0 +1,85 @@
use eyre::{bail, Context};
use std::{
fs,
path::{Path, PathBuf},
};

pub fn create(args: crate::CommandNew) -> eyre::Result<()> {
let crate::CommandNew {
kind,
lang: _,
name,
path,
} = args;

match kind {
crate::Kind::Operator => create_operator(name, path),
crate::Kind::CustomNode => create_custom_node(name, path),
}
}

fn create_operator(name: String, path: Option<PathBuf>) -> Result<(), eyre::ErrReport> {
const OPERATOR: &str = include_str!("operator/operator-template.c");
const HEADER: &str = include_str!("../../../../../apis/c/operator/operator_api.h");

if name.contains('/') {
bail!("operator name must not contain `/` separators");
}
if !name.is_ascii() {
bail!("operator name must be ASCII");
}

// create directories
let root = path.as_deref().unwrap_or(Path::new(&name));
fs::create_dir(&root)
.with_context(|| format!("failed to create directory `{}`", root.display()))?;

let operator_path = root.join("operator.c");
fs::write(&operator_path, OPERATOR)
.with_context(|| format!("failed to write `{}`", operator_path.display()))?;
let header_path = root.join("operator_api.h");
fs::write(&header_path, HEADER)
.with_context(|| format!("failed to write `{}`", header_path.display()))?;

// TODO: Makefile?

println!(
"Created new C operator `{name}` at {}",
Path::new(".").join(root).display()
);

Ok(())
}

fn create_custom_node(name: String, path: Option<PathBuf>) -> Result<(), eyre::ErrReport> {
const NODE: &str = include_str!("node/node-template.c");
const HEADER: &str = include_str!("../../../../../apis/c/node/node_api.h");

if name.contains('/') {
bail!("node name must not contain `/` separators");
}
if !name.is_ascii() {
bail!("node name must be ASCII");
}

// create directories
let root = path.as_deref().unwrap_or(Path::new(&name));
fs::create_dir(&root)
.with_context(|| format!("failed to create directory `{}`", root.display()))?;

let node_path = root.join("node.c");
fs::write(&node_path, NODE)
.with_context(|| format!("failed to write `{}`", node_path.display()))?;
let header_path = root.join("node_api.h");
fs::write(&header_path, HEADER)
.with_context(|| format!("failed to write `{}`", header_path.display()))?;

// TODO: Makefile?

println!(
"Created new C custom node `{name}` at {}",
Path::new(".").join(root).display()
);

Ok(())
}

+ 49
- 0
binaries/cli/src/template/c/node/node-template.c View File

@@ -0,0 +1,49 @@
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "node_api.h"

// sleep
#ifdef _WIN32
#include <Windows.h>
#else
#include <unistd.h>
#endif

int main()
{
void *dora_context = init_dora_context_from_env();
if (dora_context == NULL)
{
fprintf(stderr, "failed to init dora context\n");
return -1;
}

while (1)
{
void *input = dora_next_input(dora_context);
if (input == NULL)
{
// end of input
break;
}

char *id;
size_t id_len;
read_dora_input_id(input, &id, &id_len);

char *data;
size_t data_len;
read_dora_input_data(input, &data, &data_len);

char out_id[] = "foo";
char out_data[] = "bar";
dora_send_output(dora_context, out_id, strlen(out_id), out_data, strlen(out_data));

free_dora_input(input); // do not use `id` or `data` pointer after freeing
}

free_dora_context(dora_context);

return 0;
}

+ 60
- 0
binaries/cli/src/template/c/operator/operator-template.c View File

@@ -0,0 +1,60 @@
#include "operator_api.h"
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

DoraInitResult_t dora_init_operator(void)
{
// allocate memory for storing context across function calls (optional)
void *context = malloc(10);
// TODO initialize context memory

DoraInitResult_t result = {.operator_context = context};
return result;
}

DoraResult_t dora_drop_operator(void *operator_context)
{
free(operator_context);

DoraResult_t result = {};
return result;
}

OnInputResult_t dora_on_input(
const Input_t *input,
const SendOutput_t *send_output,
void *operator_context)
{
char id[input->id.len + 1];
memcpy(id, input->id.ptr, input->id.len);
id[input->id.len] = 0;

// example for matching on input name
if (strcmp(id, "foo") == 0)
{
char *out_id = "bar";
char *out_id_heap = strdup(out_id);

int data_alloc_size = 10;
void *out_data = malloc(data_alloc_size);
// TODO intialize out_data

Output_t output = {.id = {
.ptr = (uint8_t *)out_id_heap,
.len = strlen(out_id_heap),
.cap = strlen(out_id_heap) + 1,
},
.data = {.ptr = (uint8_t *)out_data, .len = strlen(out_data), .cap = data_alloc_size}};
DoraResult_t res = (send_output->send_output.call)(send_output->send_output.env_ptr, output);

OnInputResult_t result = {.result = res, .status = DORA_STATUS_CONTINUE};
return result;
}
else
{
OnInputResult_t result = {.status = DORA_STATUS_CONTINUE};
return result;
}
}

+ 1
- 0
binaries/cli/src/template/cxx.rs View File

@@ -0,0 +1 @@


+ 13
- 0
binaries/cli/src/template/mod.rs View File

@@ -0,0 +1,13 @@
mod c;
mod cxx;
mod python;
mod rust;

pub fn create(args: crate::CommandNew) -> eyre::Result<()> {
match args.lang {
crate::Lang::Rust => rust::create(args),
crate::Lang::Python => python::create(args),
crate::Lang::C => c::create(args),
crate::Lang::Cxx => todo!(),
}
}

+ 23
- 0
binaries/cli/src/template/python/mod.rs View File

@@ -0,0 +1,23 @@
use std::path::PathBuf;

pub fn create(args: crate::CommandNew) -> eyre::Result<()> {
let crate::CommandNew {
kind,
lang: _,
name,
path,
} = args;

match kind {
crate::Kind::Operator => create_operator(name, path),
crate::Kind::CustomNode => create_custom_node(name, path),
}
}

fn create_operator(name: String, path: Option<PathBuf>) -> Result<(), eyre::ErrReport> {
todo!()
}

fn create_custom_node(name: String, path: Option<PathBuf>) -> Result<(), eyre::ErrReport> {
todo!()
}

+ 91
- 0
binaries/cli/src/template/rust/mod.rs View File

@@ -0,0 +1,91 @@
use eyre::{bail, Context};
use std::{
fs,
path::{Path, PathBuf},
};

pub fn create(args: crate::CommandNew) -> eyre::Result<()> {
let crate::CommandNew {
kind,
lang: _,
name,
path,
} = args;

match kind {
crate::Kind::Operator => create_operator(name, path),
crate::Kind::CustomNode => create_custom_node(name, path),
}
}

fn create_operator(name: String, path: Option<PathBuf>) -> Result<(), eyre::ErrReport> {
const CARGO_TOML: &str = include_str!("operator/Cargo-template.toml");
const LIB_RS: &str = include_str!("operator/lib-template.rs");

if name.contains('/') {
bail!("operator name must not contain `/` separators");
}
if !name.is_ascii() {
bail!("operator name must be ASCII");
}

// create directories
let root = path.as_deref().unwrap_or(Path::new(&name));
fs::create_dir(&root)
.with_context(|| format!("failed to create directory `{}`", root.display()))?;
let src = root.join("src");
fs::create_dir(&src)
.with_context(|| format!("failed to create directory `{}`", src.display()))?;

let cargo_toml = CARGO_TOML.replace("___name___", &name);
let cargo_toml_path = root.join("Cargo.toml");
fs::write(&cargo_toml_path, &cargo_toml)
.with_context(|| format!("failed to write `{}`", cargo_toml_path.display()))?;

let lib_rs_path = src.join("lib.rs");
fs::write(&lib_rs_path, LIB_RS)
.with_context(|| format!("failed to write `{}`", lib_rs_path.display()))?;

println!(
"Created new Rust operator `{name}` at {}",
Path::new(".").join(root).display()
);

Ok(())
}

fn create_custom_node(name: String, path: Option<PathBuf>) -> Result<(), eyre::ErrReport> {
const CARGO_TOML: &str = include_str!("node/Cargo-template.toml");
const MAIN_RS: &str = include_str!("node/main-template.rs");

if name.contains('/') {
bail!("node name must not contain `/` separators");
}
if !name.is_ascii() {
bail!("node name must be ASCII");
}

// create directories
let root = path.as_deref().unwrap_or(Path::new(&name));
fs::create_dir(&root)
.with_context(|| format!("failed to create directory `{}`", root.display()))?;
let src = root.join("src");
fs::create_dir(&src)
.with_context(|| format!("failed to create directory `{}`", src.display()))?;

let cargo_toml = CARGO_TOML.replace("___name___", &name);
let cargo_toml_path = root.join("Cargo.toml");
fs::write(&cargo_toml_path, &cargo_toml)
.with_context(|| format!("failed to write `{}`", cargo_toml_path.display()))?;

let main_rs_path = src.join("main.rs");
fs::write(&main_rs_path, MAIN_RS)
.with_context(|| format!("failed to write `{}`", main_rs_path.display()))?;

println!(
"Created new Rust custom node `{name}` at {}",
Path::new(".").join(root).display()
);

Ok(())
}

+ 9
- 0
binaries/cli/src/template/rust/node/Cargo-template.toml View File

@@ -0,0 +1,9 @@
[package]
name = "___name___"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
dora-node-api = "0.1.0"

+ 15
- 0
binaries/cli/src/template/rust/node/main-template.rs View File

@@ -0,0 +1,15 @@
use dora_node_api::{self, config::DataId, DoraNode};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
let mut node = DoraNode::init_from_env()?;
let inputs = node.inputs()?;

while let Ok(input) = inputs.recv() {
match input.id.as_str() {
other => eprintln!("Ignoring unexpected input `{other}`"),
}
}

Ok(())
}

+ 12
- 0
binaries/cli/src/template/rust/operator/Cargo-template.toml View File

@@ -0,0 +1,12 @@
[package]
name = "___name___"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["cdylib"]

[dependencies]
dora-operator-api = "0.1.0"

+ 23
- 0
binaries/cli/src/template/rust/operator/lib-template.rs View File

@@ -0,0 +1,23 @@
use dora_operator_api::{register_operator, DoraOperator, DoraOutputSender, DoraStatus};
use std::time::{Duration, Instant};

register_operator!(ExampleOperator);

#[derive(Debug, Default)]
struct ExampleOperator {
example_field: u32,
}

impl DoraOperator for ExampleOperator {
fn on_input(
&mut self,
id: &str,
data: &[u8],
output_sender: &mut DoraOutputSender,
) -> Result<DoraStatus, String> {
match id {
other => eprintln!("ignoring unexpected input {other}"),
}
Ok(DoraStatus::Continue)
}
}

+ 0
- 13
binaries/coordinator/src/lib.rs View File

@@ -14,8 +14,6 @@ use tokio_stream::wrappers::IntervalStream;
#[derive(Debug, Clone, clap::Parser)]
#[clap(about = "Dora coordinator")]
pub enum Command {
#[clap(about = "Print Graph")]
Visualize { dataflow: PathBuf },
#[clap(about = "Run dataflow pipeline")]
Run {
dataflow: PathBuf,
@@ -25,17 +23,6 @@ pub enum Command {

pub async fn run(command: Command) -> eyre::Result<()> {
match command {
Command::Visualize { dataflow: file } => {
let descriptor = read_descriptor(&file).await?;
let visualized = descriptor
.visualize_as_mermaid()
.context("failed to visualize descriptor")?;
println!("{visualized}");
println!(
"Paste the above output on https://mermaid.live/ or in a \
```mermaid code block on GitHub to display it."
);
}
Command::Run { dataflow, runtime } => {
let runtime_path = runtime.unwrap_or_else(|| {
std::env::args()


+ 2
- 12
binaries/runtime/src/operator/shared_lib.rs View File

@@ -1,4 +1,5 @@
use super::OperatorEvent;
use dora_core::adjust_shared_library_path;
use dora_node_api::{communication::Publisher, config::DataId};
use dora_operator_api_types::{
safer_ffi::closure::ArcDynFn1, DoraDropOperator, DoraInitOperator, DoraInitResult, DoraOnInput,
@@ -24,18 +25,7 @@ pub fn spawn(
inputs: Receiver<dora_node_api::Input>,
publishers: HashMap<DataId, Box<dyn Publisher>>,
) -> eyre::Result<()> {
let file_name = path
.file_name()
.ok_or_else(|| eyre!("shared library path has no file name"))?
.to_str()
.ok_or_else(|| eyre!("shared library file name is not valid UTF8"))?;
if file_name.starts_with("lib") {
bail!("Shared library file name must not start with `lib`, prefix is added automatically");
}
if path.extension().is_some() {
bail!("Shared library file name must have no extension, it is added automatically");
}
let path = path.with_file_name(libloading::library_filename(file_name));
let path = adjust_shared_library_path(path)?;

let library = unsafe {
libloading::Library::new(&path)


+ 3
- 0
examples/rust-dataflow/dataflow.yml View File

@@ -5,6 +5,7 @@ communication:
nodes:
- id: rust-node
custom:
build: cargo build -p rust-dataflow-example-node
run: ../../target/debug/rust-dataflow-example-node
inputs:
tick: dora/timer/millis/300
@@ -13,6 +14,7 @@ nodes:
- id: runtime-node
operators:
- id: rust-operator
build: cargo build -p rust-dataflow-example-operator
shared-library: ../../target/debug/rust_dataflow_example_operator
inputs:
tick: dora/timer/millis/100
@@ -21,6 +23,7 @@ nodes:
- status
- id: rust-sink
custom:
build: cargo build -p rust-dataflow-example-sink
run: ../../target/debug/rust-dataflow-example-sink
inputs:
message: runtime-node/rust-operator/status

+ 15
- 4
examples/rust-dataflow/run.rs View File

@@ -7,13 +7,12 @@ async fn main() -> eyre::Result<()> {
std::env::set_current_dir(root.join(file!()).parent().unwrap())
.wrap_err("failed to set working dir")?;

build_package("rust-dataflow-example-node").await?;
build_package("rust-dataflow-example-operator").await?;
build_package("rust-dataflow-example-sink").await?;
let dataflow = Path::new("dataflow.yml");
build_dataflow(dataflow).await?;
build_package("dora-runtime").await?;

dora_coordinator::run(dora_coordinator::Command::Run {
dataflow: Path::new("dataflow.yml").to_owned(),
dataflow: dataflow.to_owned(),
runtime: Some(root.join("target").join("debug").join("dora-runtime")),
})
.await?;
@@ -21,6 +20,18 @@ async fn main() -> eyre::Result<()> {
Ok(())
}

async fn build_dataflow(dataflow: &Path) -> eyre::Result<()> {
let cargo = std::env::var("CARGO").unwrap();
let mut cmd = tokio::process::Command::new(&cargo);
cmd.arg("run");
cmd.arg("--package").arg("dora-cli");
cmd.arg("--").arg("build").arg(dataflow);
if !cmd.status().await?.success() {
bail!("failed to build dataflow");
};
Ok(())
}

async fn build_package(package: &str) -> eyre::Result<()> {
let cargo = std::env::var("CARGO").unwrap();
let mut cmd = tokio::process::Command::new(&cargo);


+ 1
- 0
libraries/core/Cargo.toml View File

@@ -10,3 +10,4 @@ license = "Apache-2.0"
dora-node-api = { version = "0.1.0", path = "../../apis/rust/node" }
eyre = "0.6.8"
serde = { version = "1.0.136", features = ["derive"] }
serde_yaml = "0.9.11"

+ 11
- 4
libraries/core/src/descriptor/mod.rs View File

@@ -1,6 +1,5 @@
use dora_node_api::config::{
CommunicationConfig, DataId, InputMapping, NodeId, NodeRunConfig, OperatorId,
};
use dora_node_api::config::{CommunicationConfig, NodeRunConfig};
pub use dora_node_api::config::{DataId, InputMapping, NodeId, OperatorId, UserInputMapping};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
@@ -15,13 +14,16 @@ mod visualize;
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Descriptor {
// see https://github.com/dtolnay/serde-yaml/issues/298
#[serde(with = "serde_yaml::with::singleton_map")]
pub communication: CommunicationConfig,
pub nodes: Vec<Node>,
}
pub const SINGLE_OPERATOR_DEFAULT_ID: &str = "op";

impl Descriptor {
pub fn resolve_aliases(&self) -> Vec<ResolvedNode> {
let default_op_id = OperatorId::from("op".to_string());
let default_op_id = OperatorId::from(SINGLE_OPERATOR_DEFAULT_ID.to_string());

let single_operator_nodes: HashMap<_, _> = self
.nodes
@@ -156,6 +158,9 @@ pub struct OperatorConfig {

#[serde(flatten)]
pub source: OperatorSource,

#[serde(default, skip_serializing_if = "Option::is_none")]
pub build: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -192,6 +197,8 @@ pub struct CustomNode {
pub run: String,
pub env: Option<BTreeMap<String, EnvValue>>,
pub working_directory: Option<BTreeMap<String, EnvValue>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub build: Option<String>,

#[serde(flatten)]
pub run_config: NodeRunConfig,


+ 26
- 0
libraries/core/src/lib.rs View File

@@ -1 +1,27 @@
use std::{
env::consts::{DLL_PREFIX, DLL_SUFFIX},
path::Path,
};

use eyre::{bail, eyre};

pub mod descriptor;

pub fn adjust_shared_library_path(path: &Path) -> Result<std::path::PathBuf, eyre::ErrReport> {
let file_name = path
.file_name()
.ok_or_else(|| eyre!("shared library path has no file name"))?
.to_str()
.ok_or_else(|| eyre!("shared library file name is not valid UTF8"))?;
if file_name.starts_with("lib") {
bail!("Shared library file name must not start with `lib`, prefix is added automatically");
}
if path.extension().is_some() {
bail!("Shared library file name must have no extension, it is added automatically");
}

let library_filename = format!("{DLL_PREFIX}{file_name}{DLL_SUFFIX}");

let path = path.with_file_name(library_filename);
Ok(path)
}

Loading…
Cancel
Save