mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
feat: add rust (#4253)
* Add rust on frontend * Add parser for rust * Handle rust job execution * Main has to return a result that is serializable * Update parser: parse many rust types * Rust parser dependencies * Frontend wasm parser * Add windmill parser rust to windmill parser wasm (temporarily) * Add rust as a tag on worker__default * Change init code for rust * Cleanup rust_executor.rs * Remove tree-sitter dep and fix unused imports * Add lockfile and build logic * Add cargo and rustup to windmill image * Fix env var to work on docker image and dev env * modify package.json * Deps after parser publish * Add stashed migration * Unify rust versions * Add error message when php or cargo are not present to use another image * Error message conditionally on feature flag * all ci/cd changes * all ci/cd changes --------- Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
This commit is contained in:
@@ -1,7 +1,11 @@
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.event_name != 'pull_request' && github.repository || 'windmill-labs/windmill-test' }}
|
||||
DEV_SHA: ${{ github.event_name != 'pull_request' && 'dev' || format('pr-{0}', github.event.number) }}
|
||||
IMAGE_NAME:
|
||||
${{ github.event_name != 'pull_request' && github.repository ||
|
||||
'windmill-labs/windmill-test' }}
|
||||
DEV_SHA:
|
||||
${{ github.event_name != 'pull_request' && 'dev' || format('pr-{0}',
|
||||
github.event.number) }}
|
||||
|
||||
name: Build windmill:main
|
||||
on:
|
||||
@@ -495,7 +499,7 @@ jobs:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
- name: Docker meta
|
||||
id: meta-ee-public
|
||||
uses: docker/metadata-action@v4
|
||||
@@ -541,7 +545,7 @@ jobs:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
- name: get git hash
|
||||
if: github.event_name != 'pull_request'
|
||||
id: git_hash
|
||||
@@ -610,6 +614,51 @@ jobs:
|
||||
${{ steps.meta-ee-public.outputs.labels }}
|
||||
org.opencontainers.image.licenses=Windmill-Enterprise-License
|
||||
|
||||
build_slim:
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
needs: [build]
|
||||
runs-on: ubicloud
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v2
|
||||
|
||||
- uses: depot/setup-action@v1
|
||||
|
||||
- name: Docker meta
|
||||
id: meta-ee-public
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-slim
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push publicly ee
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
file: "./docker/DockerfileSlim"
|
||||
tags: |
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
labels: |
|
||||
${{ steps.meta-ee-public.outputs.labels }}
|
||||
|
||||
build_ee_slim:
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
needs: [build_ee]
|
||||
@@ -649,7 +698,98 @@ jobs:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
file: "./docker/DockerfileSlim"
|
||||
file: "./docker/DockerfileSlimEe"
|
||||
tags: |
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
labels: |
|
||||
${{ steps.meta-ee-public.outputs.labels }}
|
||||
org.opencontainers.image.licenses=Windmill-Enterprise-License
|
||||
|
||||
build_full:
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
needs: [build]
|
||||
runs-on: ubicloud
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v2
|
||||
|
||||
- uses: depot/setup-action@v1
|
||||
|
||||
- name: Docker meta
|
||||
id: meta-ee-public
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-full
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push publicly ee
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
file: "./docker/DockerfileRust"
|
||||
tags: |
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
labels: |
|
||||
${{ steps.meta-ee-public.outputs.labels }}
|
||||
|
||||
build_ee_full:
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
needs: [build_ee]
|
||||
runs-on: ubicloud
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# - name: Set up Docker Buildx
|
||||
# uses: docker/setup-buildx-action@v2
|
||||
|
||||
- uses: depot/setup-action@v1
|
||||
|
||||
- name: Docker meta
|
||||
id: meta-ee-public
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-ee-full
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push publicly ee
|
||||
uses: depot/build-push-action@v1
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
file: "./docker/DockerfileRustEe"
|
||||
tags: |
|
||||
${{ steps.meta-ee-public.outputs.tags }}
|
||||
labels: |
|
||||
|
||||
@@ -171,6 +171,9 @@ COPY --from=composer:2.7.6 /usr/bin/composer /usr/bin/composer
|
||||
# add the docker client to call docker from a worker if enabled
|
||||
COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
|
||||
|
||||
ENV RUSTUP_HOME="/usr/local/rustup"
|
||||
ENV CARGO_HOME="/usr/local/cargo"
|
||||
|
||||
WORKDIR ${APP}
|
||||
|
||||
RUN ln -s ${APP}/windmill /usr/local/bin/windmill
|
||||
|
||||
+2
-1
@@ -56,7 +56,8 @@
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php",
|
||||
"bunnative"
|
||||
"bunnative",
|
||||
"rust"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -50,7 +50,8 @@
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php",
|
||||
"bunnative"
|
||||
"bunnative",
|
||||
"rust"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -70,7 +70,8 @@
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php",
|
||||
"bunnative"
|
||||
"bunnative",
|
||||
"rust"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -55,7 +55,8 @@
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php",
|
||||
"bunnative"
|
||||
"bunnative",
|
||||
"rust"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -36,7 +36,8 @@
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php",
|
||||
"bunnative"
|
||||
"bunnative",
|
||||
"rust"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -55,7 +55,8 @@
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php",
|
||||
"bunnative"
|
||||
"bunnative",
|
||||
"rust"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -50,7 +50,8 @@
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php",
|
||||
"bunnative"
|
||||
"bunnative",
|
||||
"rust"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -76,7 +76,8 @@
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php",
|
||||
"bunnative"
|
||||
"bunnative",
|
||||
"rust"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -55,7 +55,8 @@
|
||||
"powershell",
|
||||
"mssql",
|
||||
"php",
|
||||
"bunnative"
|
||||
"bunnative",
|
||||
"rust"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+32
@@ -6379,6 +6379,18 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"getopts",
|
||||
"memchr",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulp"
|
||||
version = "0.18.22"
|
||||
@@ -10471,6 +10483,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"pg-embed",
|
||||
"prometheus",
|
||||
"quote",
|
||||
"rand 0.8.5",
|
||||
"reqwest 0.12.7",
|
||||
"rsmq_async",
|
||||
@@ -10772,6 +10785,23 @@ dependencies = [
|
||||
"windmill-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.387.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
"itertools 0.10.5",
|
||||
"lazy_static",
|
||||
"pulldown-cmark",
|
||||
"quote",
|
||||
"regex",
|
||||
"serde_json",
|
||||
"syn 2.0.76",
|
||||
"toml 0.7.8",
|
||||
"windmill-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.387.1"
|
||||
@@ -10817,6 +10847,7 @@ dependencies = [
|
||||
"windmill-parser-graphql",
|
||||
"windmill-parser-php",
|
||||
"windmill-parser-py",
|
||||
"windmill-parser-rust",
|
||||
"windmill-parser-sql",
|
||||
"windmill-parser-ts",
|
||||
]
|
||||
@@ -10931,6 +10962,7 @@ dependencies = [
|
||||
"windmill-parser-php",
|
||||
"windmill-parser-py",
|
||||
"windmill-parser-py-imports",
|
||||
"windmill-parser-rust",
|
||||
"windmill-parser-sql",
|
||||
"windmill-parser-ts",
|
||||
"windmill-queue",
|
||||
|
||||
@@ -18,6 +18,7 @@ members = [
|
||||
"./parsers/windmill-parser-ts",
|
||||
"./parsers/windmill-parser-wasm",
|
||||
"./parsers/windmill-parser-go",
|
||||
"./parsers/windmill-parser-rust",
|
||||
"./parsers/windmill-parser-bash",
|
||||
"./parsers/windmill-parser-py",
|
||||
"./parsers/windmill-parser-py-imports",
|
||||
@@ -85,6 +86,7 @@ serde_json.workspace = true
|
||||
serde.workspace = true
|
||||
deno_core.workspace = true
|
||||
pg-embed = {git = "https://github.com/faokunega/pg-embed", optional = true, default-features = false, features = ['rt_tokio']}
|
||||
quote.workspace = true
|
||||
|
||||
|
||||
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||
@@ -114,6 +116,7 @@ windmill-parser-ts = { path = "./parsers/windmill-parser-ts" }
|
||||
windmill-parser-py = { path = "./parsers/windmill-parser-py" }
|
||||
windmill-parser-py-imports = { path = "./parsers/windmill-parser-py-imports" }
|
||||
windmill-parser-go = { path = "./parsers/windmill-parser-go" }
|
||||
windmill-parser-rust = { path = "./parsers/windmill-parser-rust" }
|
||||
windmill-parser-bash = { path = "./parsers/windmill-parser-bash" }
|
||||
windmill-parser-sql = { path = "./parsers/windmill-parser-sql" }
|
||||
windmill-parser-graphql = { path = "./parsers/windmill-parser-graphql" }
|
||||
@@ -266,4 +269,9 @@ tikv-jemalloc-ctl = { version = "^0.5" }
|
||||
triomphe = "<0.1.12"
|
||||
|
||||
tantivy = "0.22.0"
|
||||
|
||||
pulldown-cmark = "0.9"
|
||||
toml = "0.7"
|
||||
syn = { version = "2.0.74", features = ["full"] }
|
||||
quote = "1.0.36"
|
||||
regex-lite = "0.1.6"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add up migration script here
|
||||
ALTER TYPE SCRIPT_LANG ADD VALUE IF NOT EXISTS 'rust';
|
||||
UPDATE config set config = jsonb_set(config, '{worker_tags}', config->'worker_tags' || '["rust"]'::jsonb) where name = 'worker__default' and config @> '{"worker_tags": ["deno", "python3", "go", "bash", "powershell", "dependency", "flow", "hub", "other", "bun", "php"]}'::jsonb AND NOT config->'worker_tags' @> '"rust"'::jsonb;
|
||||
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "windmill-parser-rust"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_parser_rust"
|
||||
path = "./src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
windmill-parser.workspace = true
|
||||
itertools.workspace = true
|
||||
serde_json.workspace = true
|
||||
anyhow.workspace = true
|
||||
convert_case.workspace = true
|
||||
lazy_static.workspace = true
|
||||
regex.workspace = true
|
||||
pulldown-cmark.workspace = true
|
||||
toml.workspace = true
|
||||
syn.workspace = true
|
||||
quote.workspace = true
|
||||
@@ -0,0 +1,16 @@
|
||||
[[bin]]
|
||||
name = "main"
|
||||
path = "./main.rs"
|
||||
|
||||
[package]
|
||||
authors = ["Anonymous"]
|
||||
edition = "2021"
|
||||
name = "main"
|
||||
version = "0.1.0"
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.207", features = ["derive"] }
|
||||
serde_json = "1.0.124"
|
||||
@@ -0,0 +1,501 @@
|
||||
use anyhow::anyhow;
|
||||
use itertools::Itertools;
|
||||
use quote::ToTokens;
|
||||
use regex::Regex;
|
||||
use windmill_parser::{Arg, MainArgSignature, Typ};
|
||||
|
||||
pub fn otyp_to_string(otyp: Option<String>) -> String {
|
||||
otyp.unwrap()
|
||||
}
|
||||
|
||||
pub fn parse_rust_signature(code: &str) -> anyhow::Result<MainArgSignature> {
|
||||
let ast: syn::File = syn::parse_file(code)?;
|
||||
|
||||
if let Some(main_fn) = ast.items.iter().find_map(|item| match item {
|
||||
syn::Item::Fn(f) if f.sig.ident == "main" => Some(f),
|
||||
_ => None,
|
||||
}) {
|
||||
let args = main_fn
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.map(|param| {
|
||||
let (otyp, typ, name) = parse_rust_typ(param);
|
||||
Arg { name, otyp, typ, default: None, has_default: false, oidx: None }
|
||||
})
|
||||
.collect_vec();
|
||||
Ok(MainArgSignature {
|
||||
star_args: false,
|
||||
star_kwargs: false,
|
||||
args,
|
||||
no_main_func: Some(false),
|
||||
})
|
||||
} else {
|
||||
Ok(MainArgSignature {
|
||||
star_args: false,
|
||||
star_kwargs: false,
|
||||
args: vec![],
|
||||
no_main_func: Some(true),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_rust_deps_into_manifest(code: &str) -> anyhow::Result<String> {
|
||||
const MODIFIABLE_MANIFEST_TABLES: &[&str] = &["dependencies"];
|
||||
|
||||
let partial_manifest = find_embedded_manifest(code).unwrap_or(Manifest::Toml("".to_string()));
|
||||
let partial_manifest = partial_manifest.into_toml()?;
|
||||
let mut manif = default_manifest();
|
||||
for table_name in MODIFIABLE_MANIFEST_TABLES {
|
||||
match partial_manifest.get(*table_name) {
|
||||
Some(toml::Value::Table(tab)) => {
|
||||
// Merge.
|
||||
match manif.entry(*table_name) {
|
||||
toml::map::Entry::Vacant(e) => {
|
||||
e.insert(toml::Value::Table(tab.to_owned()));
|
||||
}
|
||||
toml::map::Entry::Occupied(e) => {
|
||||
let into_t = match e.into_mut() {
|
||||
toml::Value::Table(t) => Some(t),
|
||||
_ => None,
|
||||
};
|
||||
into_t.ok_or(anyhow!(""))?.extend(tab.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(v) => {
|
||||
// Just replace.
|
||||
manif.insert(table_name.to_string(), v.to_owned());
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(manif.to_string())
|
||||
}
|
||||
|
||||
fn parse_pat_type(p: Box<syn::Type>) -> Typ {
|
||||
match *p {
|
||||
syn::Type::Array(a) => {
|
||||
let inner_typ = parse_pat_type(a.elem);
|
||||
Typ::List(Box::new(inner_typ))
|
||||
}
|
||||
// syn::Type::BareFn(_) => todo!(),
|
||||
// syn::Type::Group(_) => todo!(),
|
||||
// syn::Type::ImplTrait(_) => todo!(),
|
||||
// syn::Type::Infer(_) => todo!(),
|
||||
// syn::Type::Macro(_) => todo!(),
|
||||
// syn::Type::Never(_) => todo!(),
|
||||
syn::Type::Paren(e) => parse_pat_type(e.elem),
|
||||
syn::Type::Path(e) => {
|
||||
if let Some(u) = e.path.segments.last() {
|
||||
match u.ident.to_string().as_str() {
|
||||
"usize" | "u8" | "u16" | "u32" | "u64" | "u128" | "isize" | "i8" | "i16"
|
||||
| "i32" | "i64" | "i128" => Typ::Int,
|
||||
"String" | "str" => Typ::Str(None),
|
||||
"bool" => Typ::Bool,
|
||||
"f32" | "f64" => Typ::Float,
|
||||
"Vec" => {
|
||||
if let syn::PathArguments::AngleBracketed(t) = &u.arguments {
|
||||
if let Some(syn::GenericArgument::Type(a)) = t.args.last() {
|
||||
Typ::List(Box::new(parse_pat_type(Box::new(a.clone()))))
|
||||
} else {
|
||||
Typ::Unknown
|
||||
}
|
||||
} else {
|
||||
Typ::Unknown
|
||||
}
|
||||
}
|
||||
_ => Typ::Unknown,
|
||||
}
|
||||
} else {
|
||||
Typ::Unknown
|
||||
}
|
||||
}
|
||||
// syn::Type::Ptr(_) => todo!(),
|
||||
syn::Type::Reference(e) => parse_pat_type(e.elem),
|
||||
syn::Type::Slice(e) => Typ::List(Box::new(parse_pat_type(e.elem))),
|
||||
// syn::Type::TraitObject(_) => todo!(),
|
||||
// syn::Type::Tuple(_) => todo!(),
|
||||
// syn::Type::Verbatim(_) => todo!(),
|
||||
_ => Typ::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_rust_typ(param_typ: &syn::FnArg) -> (Option<String>, Typ, String) {
|
||||
match param_typ {
|
||||
syn::FnArg::Receiver(_) => (None, Typ::Unknown, "self".to_string()),
|
||||
syn::FnArg::Typed(s) => {
|
||||
let name = match *s.pat.clone() {
|
||||
syn::Pat::Ident(p) => p.ident.to_string(),
|
||||
_ => "undefined".to_string(),
|
||||
};
|
||||
|
||||
let otyp = Some(s.ty.to_token_stream().to_string());
|
||||
let typ = parse_pat_type(s.ty.clone());
|
||||
(otyp, typ, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Manifest {
|
||||
Toml(String),
|
||||
DepList(String),
|
||||
}
|
||||
|
||||
impl Manifest {
|
||||
pub fn into_toml(self) -> anyhow::Result<toml::value::Table> {
|
||||
match self {
|
||||
Manifest::Toml(s) => toml::from_str(&s),
|
||||
Manifest::DepList(s) => Manifest::dep_list_to_toml(&s),
|
||||
}
|
||||
.map_err(|e| anyhow!("Could not parse embedded manifest: {}", e))
|
||||
}
|
||||
|
||||
fn dep_list_to_toml(s: &str) -> ::std::result::Result<toml::value::Table, toml::de::Error> {
|
||||
let mut r = String::new();
|
||||
r.push_str("[dependencies]\n");
|
||||
for dep in s.trim().split(',') {
|
||||
// If there's no version specified, add one.
|
||||
match dep.contains('=') {
|
||||
true => {
|
||||
r.push_str(dep);
|
||||
r.push('\n');
|
||||
}
|
||||
false => {
|
||||
r.push_str(dep);
|
||||
r.push_str("=\"*\"\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toml::from_str(&r)
|
||||
}
|
||||
}
|
||||
|
||||
fn default_manifest() -> toml::value::Table {
|
||||
toml::from_str(include_str!("../manifest/Cargo.toml.default"))
|
||||
.expect("Failed to parse Cargo.toml.default")
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a `Manifest` that can be made into a TOML table. The format and logic are the same as in
|
||||
* the rust-script or cargo-eval projects.
|
||||
*/
|
||||
fn find_embedded_manifest(s: &str) -> Option<Manifest> {
|
||||
find_short_hand_manifest(s).or_else(|| find_code_block_manifest(s))
|
||||
}
|
||||
|
||||
fn find_short_hand_manifest(s: &str) -> Option<Manifest> {
|
||||
let re: Regex = Regex::new(r"^(?i)\s*//\s*cargo-deps\s*:(.*?)(\r\n|\n)").unwrap();
|
||||
/*
|
||||
This is pretty simple: the only valid syntax for this is for the first, non-blank line to contain a single-line comment whose first token is `cargo-deps:`. That's it.
|
||||
*/
|
||||
if let Some(cap) = re.captures(s) {
|
||||
if let Some(m) = cap.get(1) {
|
||||
return Some(Manifest::DepList(m.as_str().to_string()));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/**
|
||||
Locates a "code block manifest" in Rust source.
|
||||
*/
|
||||
fn find_code_block_manifest(s: &str) -> Option<Manifest> {
|
||||
let re_crate_comment: Regex = {
|
||||
Regex::new(
|
||||
r"(?x)
|
||||
^\s*
|
||||
(/\*!|//(!|/))
|
||||
",
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let start = match re_crate_comment.captures(s) {
|
||||
Some(cap) => match cap.get(1) {
|
||||
Some(m) => m.start(),
|
||||
None => return None,
|
||||
},
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let comment = match extract_comment(&s[start..]) {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
scrape_markdown_manifest(&comment).map(|s| Manifest::Toml(s))
|
||||
}
|
||||
|
||||
/**
|
||||
Extracts the first `Cargo` fenced code block from a chunk of Markdown.
|
||||
*/
|
||||
fn scrape_markdown_manifest(content: &str) -> Option<String> {
|
||||
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
|
||||
|
||||
// To match librustdoc/html/markdown.rs, opts.
|
||||
let exts = Options::ENABLE_TABLES | Options::ENABLE_FOOTNOTES;
|
||||
|
||||
let md = Parser::new_ext(content, exts);
|
||||
|
||||
let mut found = false;
|
||||
let mut output = None;
|
||||
|
||||
for item in md {
|
||||
match item {
|
||||
Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(ref info)))
|
||||
if info.to_lowercase() == "cargo" && output.is_none() =>
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
Event::Text(ref text) if found => {
|
||||
let s = output.get_or_insert(String::new());
|
||||
s.push_str(text);
|
||||
}
|
||||
Event::End(Tag::CodeBlock(_)) if found => {
|
||||
found = false;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/**
|
||||
Extracts the contents of a Rust doc comment.
|
||||
*/
|
||||
fn extract_comment(s: &str) -> Option<String> {
|
||||
use std::cmp::min;
|
||||
|
||||
fn extract_block(s: &str) -> Option<String> {
|
||||
/*
|
||||
On every line:
|
||||
|
||||
- update nesting level and detect end-of-comment
|
||||
- if margin is None:
|
||||
- if there appears to be a margin, set margin.
|
||||
- strip off margin marker
|
||||
- update the leading space counter
|
||||
- strip leading space
|
||||
- append content
|
||||
*/
|
||||
let mut r = String::new();
|
||||
|
||||
let margin_re: Regex = Regex::new(r"^\s*\*( |$)").unwrap();
|
||||
let space_re: Regex = Regex::new(r"^(\s+)").unwrap();
|
||||
let nesting_re: Regex = Regex::new(r"/\*|\*/").unwrap();
|
||||
|
||||
let mut leading_space = None;
|
||||
let mut margin = None;
|
||||
let mut depth: u32 = 1;
|
||||
|
||||
for line in s.lines() {
|
||||
if depth == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut end_of_comment = None;
|
||||
|
||||
for (end, marker) in nesting_re.find_iter(line).map(|m| (m.start(), m.as_str())) {
|
||||
match (marker, depth) {
|
||||
("/*", _) => depth += 1,
|
||||
("*/", 1) => {
|
||||
end_of_comment = Some(end);
|
||||
depth = 0;
|
||||
break;
|
||||
}
|
||||
("*/", _) => depth -= 1,
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
||||
let line = end_of_comment.map(|end| &line[..end]).unwrap_or(line);
|
||||
|
||||
margin = margin.or_else(|| margin_re.find(line).map(|m| m.as_str()));
|
||||
|
||||
let line = if let Some(margin) = margin {
|
||||
let end = line
|
||||
.char_indices()
|
||||
.take(margin.len())
|
||||
.map(|(i, c)| i + c.len_utf8())
|
||||
.last()
|
||||
.unwrap_or(0);
|
||||
&line[end..]
|
||||
} else {
|
||||
line
|
||||
};
|
||||
|
||||
leading_space = leading_space.or_else(|| space_re.find(line).map(|m| m.end()));
|
||||
|
||||
let strip_len = min(leading_space.unwrap_or(0), line.len());
|
||||
let line = &line[strip_len..];
|
||||
|
||||
r.push_str(line);
|
||||
|
||||
r.push('\n');
|
||||
}
|
||||
|
||||
Some(r)
|
||||
}
|
||||
|
||||
fn extract_line(s: &str) -> Option<String> {
|
||||
let mut r = String::new();
|
||||
|
||||
let comment_re = Regex::new(r"^\s*//(!|/)").unwrap();
|
||||
|
||||
let space_re = Regex::new(r"^(\s+)").unwrap();
|
||||
|
||||
let mut leading_space = None;
|
||||
|
||||
for line in s.lines() {
|
||||
let content = match comment_re.find(line) {
|
||||
Some(m) => &line[m.end()..],
|
||||
None => break,
|
||||
};
|
||||
|
||||
leading_space = leading_space.or_else(|| {
|
||||
space_re
|
||||
.captures(content)
|
||||
.and_then(|c| c.get(1))
|
||||
.map(|m| m.end())
|
||||
});
|
||||
|
||||
let strip_len = min(leading_space.unwrap_or(0), content.len());
|
||||
let content = &content[strip_len..];
|
||||
|
||||
r.push_str(content);
|
||||
|
||||
r.push('\n');
|
||||
}
|
||||
|
||||
Some(r)
|
||||
}
|
||||
|
||||
if let Some(stripped) = s.strip_prefix("/*!") {
|
||||
extract_block(stripped)
|
||||
} else if s.starts_with("//!") || s.starts_with("///") {
|
||||
extract_line(s)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_parse_rust_signature() {
|
||||
let code = r#"
|
||||
// commenting comments
|
||||
|
||||
fn main(
|
||||
my_int: i8,
|
||||
my_vec: Vec<u8>,
|
||||
mut my_arr: [u8; 9],
|
||||
my_complex: Vec<Result<MyStruct, anyhow::Error>>,
|
||||
) -> Result<String, String> {
|
||||
println!("My int is {}", my_int);
|
||||
}"#;
|
||||
|
||||
let ret = parse_rust_signature(code).unwrap();
|
||||
|
||||
assert_eq!(ret.args.len(), 4);
|
||||
|
||||
assert_eq!(ret.args[0].name, "my_int");
|
||||
assert_eq!(ret.args[0].otyp, Some("i8".to_string()));
|
||||
assert_eq!(ret.args[0].typ, Typ::Int);
|
||||
|
||||
assert_eq!(ret.args[1].name, "my_vec");
|
||||
assert_eq!(ret.args[1].otyp, Some("Vec < u8 >".to_string()));
|
||||
assert_eq!(ret.args[1].typ, Typ::List(Box::new(Typ::Int)));
|
||||
|
||||
assert_eq!(ret.args[2].name, "my_arr");
|
||||
assert_eq!(ret.args[2].otyp, Some("[u8 ; 9]".to_string()));
|
||||
assert_eq!(ret.args[2].typ, Typ::List(Box::new(Typ::Int)));
|
||||
|
||||
assert_eq!(ret.args[3].name, "my_complex");
|
||||
assert_eq!(
|
||||
ret.args[3].otyp,
|
||||
Some("Vec < Result < MyStruct , anyhow :: Error > >".to_string())
|
||||
);
|
||||
assert_eq!(ret.args[3].typ, Typ::List(Box::new(Typ::Unknown)));
|
||||
|
||||
let code = r#"
|
||||
// commenting comments
|
||||
|
||||
fn main(
|
||||
my_str_slice: &str,
|
||||
my_String: String,
|
||||
mut my_mut_ref_to_string: &mut String,
|
||||
my_string_vec: Vec<String>,
|
||||
) -> Result<String, String> {
|
||||
println!("My int is {}", my_int);
|
||||
}"#;
|
||||
|
||||
let ret = parse_rust_signature(code).unwrap();
|
||||
|
||||
assert_eq!(ret.args.len(), 4);
|
||||
|
||||
assert_eq!(ret.args[0].name, "my_str_slice");
|
||||
assert_eq!(ret.args[0].otyp, Some("& str".to_string()));
|
||||
assert_eq!(ret.args[0].typ, Typ::Str(None));
|
||||
|
||||
assert_eq!(ret.args[1].name, "my_String");
|
||||
assert_eq!(ret.args[1].otyp, Some("String".to_string()));
|
||||
assert_eq!(ret.args[1].typ, Typ::Str(None));
|
||||
|
||||
assert_eq!(ret.args[2].name, "my_mut_ref_to_string");
|
||||
assert_eq!(ret.args[2].otyp, Some("& mut String".to_string()));
|
||||
assert_eq!(ret.args[2].typ, Typ::Str(None));
|
||||
|
||||
assert_eq!(ret.args[3].name, "my_string_vec");
|
||||
assert_eq!(
|
||||
ret.args[3].otyp,
|
||||
Some("Vec < String >".to_string())
|
||||
);
|
||||
assert_eq!(ret.args[3].typ, Typ::List(Box::new(Typ::Str(None))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_rust_manifest() {
|
||||
assert_eq!(find_embedded_manifest("fn main() {}"), None);
|
||||
|
||||
let code = r#"
|
||||
|
||||
//! Crate doc comment right here
|
||||
//! We need to use the `cargo` language in the code bloc
|
||||
//!
|
||||
//! ```cargo
|
||||
//! [dependencies]
|
||||
//! time = "0.1.25"
|
||||
//! ```
|
||||
|
||||
fn main() {
|
||||
println!("{}", time::now().rfc822z());
|
||||
}
|
||||
"#;
|
||||
let manif = find_embedded_manifest(code);
|
||||
|
||||
assert_eq!(
|
||||
manif,
|
||||
Some(Manifest::Toml(
|
||||
r#"[dependencies]
|
||||
time = "0.1.25"
|
||||
"#
|
||||
.to_string()
|
||||
))
|
||||
);
|
||||
let _full_manif = parse_rust_deps_into_manifest(code).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_manifest() {
|
||||
default_manifest();
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,6 @@ crate-type = ["cdylib"]
|
||||
name = "windmill_parser_wasm"
|
||||
path = "./src/lib.rs"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 's'
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test.workspace = true
|
||||
windmill-parser-ts.workspace = true
|
||||
@@ -27,6 +23,7 @@ sql-parser = [ "dep:windmill-parser-sql"]
|
||||
py-parser = [ "dep:windmill-parser-py"]
|
||||
ts-parser = [ "dep:windmill-parser-ts"]
|
||||
php-parser = [ "dep:windmill-parser-php"]
|
||||
rust-parser = [ "dep:windmill-parser-rust"]
|
||||
graphql-parser = [ "dep:windmill-parser-graphql"]
|
||||
|
||||
[dependencies]
|
||||
@@ -39,6 +36,7 @@ windmill-parser-py = { workspace = true, optional = true }
|
||||
windmill-parser-ts = { workspace = true, optional = true }
|
||||
windmill-parser-php = { workspace = true, optional = true }
|
||||
windmill-parser-graphql = { workspace = true, optional = true }
|
||||
windmill-parser-rust = { workspace = true, optional = true }
|
||||
wasm-bindgen.workspace = true
|
||||
serde_json.workspace = true
|
||||
getrandom = { workspace = true, features = ["js"] }
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
### Windmill parser wasm
|
||||
|
||||
How to build
|
||||
|
||||
```
|
||||
wasm-pack build --release --target web
|
||||
```
|
||||
|
||||
#### Prerequisite
|
||||
|
||||
Install wasm-pack
|
||||
|
||||
```
|
||||
cargo install wasm-pack
|
||||
```
|
||||
|
||||
#### To use it on a dev environment
|
||||
|
||||
Go to frontend and run:
|
||||
|
||||
```
|
||||
npm install ../backend/parsers/windmill-parser-wasm/pkg
|
||||
```
|
||||
|
||||
Make sure to not reset the package.json before commiting
|
||||
@@ -36,3 +36,9 @@ OUT_DIR="pkg-php"
|
||||
wasm-pack build --release --target web --out-dir $OUT_DIR --features "php-parser" \
|
||||
-Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort
|
||||
sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-php"/' $OUT_DIR/package.json
|
||||
|
||||
# rust
|
||||
OUT_DIR="pkg-rust"
|
||||
wasm-pack build --release --target web --out-dir $OUT_DIR --features "rust-parser" \
|
||||
-Z build-std=panic_abort,std -Z build-std-features=panic_immediate_abort
|
||||
sed -i 's/"windmill-parser-wasm"/"windmill-parser-wasm-rust"/' $OUT_DIR/package.json
|
||||
|
||||
@@ -75,6 +75,11 @@ export function parse_graphql(code: string): string;
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parse_php(code: string): string;
|
||||
/**
|
||||
* @param {string} code
|
||||
* @returns {string}
|
||||
*/
|
||||
export function parse_rust(code: string): string;
|
||||
|
||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
|
||||
@@ -95,6 +100,7 @@ export interface InitOutput {
|
||||
readonly parse_db_resource: (a: number, b: number, c: number) => void;
|
||||
readonly parse_graphql: (a: number, b: number, c: number) => void;
|
||||
readonly parse_php: (a: number, b: number, c: number) => void;
|
||||
readonly parse_rust: (a: number, b: number, c: number) => void;
|
||||
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
||||
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
|
||||
|
||||
@@ -16,6 +16,7 @@ export function parse_mssql(a: number, b: number, c: number): void;
|
||||
export function parse_db_resource(a: number, b: number, c: number): void;
|
||||
export function parse_graphql(a: number, b: number, c: number): void;
|
||||
export function parse_php(a: number, b: number, c: number): void;
|
||||
export function parse_rust(a: number, b: number, c: number): void;
|
||||
export function __wbindgen_malloc(a: number, b: number): number;
|
||||
export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number;
|
||||
export function __wbindgen_add_to_stack_pointer(a: number): number;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -eou pipefail
|
||||
script_dirpath="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
args=${1:-}
|
||||
|
||||
# bun and deno
|
||||
pushd "pkg-ts" && npm publish ${args}
|
||||
popd
|
||||
|
||||
@@ -19,3 +18,6 @@ popd
|
||||
|
||||
pushd "pkg-php" && npm publish ${args}
|
||||
popd
|
||||
|
||||
pushd "pkg-rust" && npm publish ${args}
|
||||
popd
|
||||
|
||||
@@ -116,3 +116,9 @@ pub fn parse_graphql(code: &str) -> String {
|
||||
pub fn parse_php(code: &str) -> String {
|
||||
wrap_sig(windmill_parser_php::parse_php_signature(code, None))
|
||||
}
|
||||
|
||||
#[cfg(feature = "rust-parser")]
|
||||
#[wasm_bindgen]
|
||||
pub fn parse_rust(code: &str) -> String {
|
||||
wrap_sig(windmill_parser_rust::parse_rust_signature(code))
|
||||
}
|
||||
|
||||
+2
-1
@@ -64,7 +64,7 @@ use windmill_worker::{
|
||||
get_hub_script_content_and_requirements, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR,
|
||||
BUN_DEPSTAR_CACHE_DIR, DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS, DENO_CACHE_DIR_NPM,
|
||||
GO_BIN_CACHE_DIR, GO_CACHE_DIR, LOCK_CACHE_DIR, PIP_CACHE_DIR, POWERSHELL_CACHE_DIR,
|
||||
TAR_PIP_CACHE_DIR, TMP_LOGS_DIR,
|
||||
RUST_CACHE_DIR, TAR_PIP_CACHE_DIR, TMP_LOGS_DIR,
|
||||
};
|
||||
|
||||
use crate::monitor::{
|
||||
@@ -832,6 +832,7 @@ pub async fn run_workers<R: rsmq_async::RsmqConnection + Send + Sync + Clone + '
|
||||
BUN_BUNDLE_CACHE_DIR,
|
||||
GO_CACHE_DIR,
|
||||
GO_BIN_CACHE_DIR,
|
||||
RUST_CACHE_DIR,
|
||||
HUB_CACHE_DIR,
|
||||
POWERSHELL_CACHE_DIR,
|
||||
] {
|
||||
|
||||
@@ -1741,6 +1741,41 @@ func main(derp string) (string, error) {
|
||||
assert_eq!(result, serde_json::json!("hello world"));
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_rust_job(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await;
|
||||
let port = server.addr.port();
|
||||
|
||||
let content = r#"
|
||||
fn main(world: String) -> Result<String, String> {
|
||||
println!("Which world to greet today?");
|
||||
Ok(format!("Hello {}!", world))
|
||||
}
|
||||
"#
|
||||
.to_owned();
|
||||
|
||||
let result = RunJob::from(JobPayload::Code(RawCode {
|
||||
hash: None,
|
||||
content,
|
||||
path: None,
|
||||
lock: None,
|
||||
language: ScriptLang::Rust,
|
||||
custom_concurrency_key: None,
|
||||
concurrent_limit: None,
|
||||
concurrency_time_window_s: None,
|
||||
cache_ttl: None,
|
||||
dedicated_worker: None,
|
||||
}))
|
||||
.arg("world", json!("Hyrule"))
|
||||
.run_until_complete(&db, port)
|
||||
.await
|
||||
.json_result()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result, serde_json::json!("Hello Hyrule!"));
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_bash_job(db: Pool<Postgres>) {
|
||||
initialize_tracing().await;
|
||||
|
||||
@@ -9187,6 +9187,7 @@ components:
|
||||
nativets,
|
||||
bun,
|
||||
php,
|
||||
rust,
|
||||
]
|
||||
kind:
|
||||
type: string
|
||||
@@ -9285,6 +9286,7 @@ components:
|
||||
nativets,
|
||||
bun,
|
||||
php,
|
||||
rust,
|
||||
]
|
||||
kind:
|
||||
type: string
|
||||
@@ -9502,6 +9504,7 @@ components:
|
||||
nativets,
|
||||
bun,
|
||||
php,
|
||||
rust,
|
||||
]
|
||||
email:
|
||||
type: string
|
||||
@@ -9617,6 +9620,7 @@ components:
|
||||
nativets,
|
||||
bun,
|
||||
php,
|
||||
rust,
|
||||
]
|
||||
is_skipped:
|
||||
type: boolean
|
||||
@@ -10155,7 +10159,8 @@ components:
|
||||
graphql,
|
||||
nativets,
|
||||
bun,
|
||||
php
|
||||
php,
|
||||
rust,
|
||||
]
|
||||
tag:
|
||||
type: string
|
||||
@@ -11285,7 +11290,8 @@ components:
|
||||
graphql,
|
||||
nativets,
|
||||
bun,
|
||||
php
|
||||
php,
|
||||
rust,
|
||||
]
|
||||
required:
|
||||
- raw_code
|
||||
|
||||
@@ -580,6 +580,7 @@ async fn create_script_internal<'c>(
|
||||
|| ns.language == ScriptLang::Bun
|
||||
|| ns.language == ScriptLang::Bunnative
|
||||
|| ns.language == ScriptLang::Deno
|
||||
|| ns.language == ScriptLang::Rust
|
||||
|| ns.language == ScriptLang::Php)
|
||||
{
|
||||
Some(String::new())
|
||||
|
||||
@@ -2576,6 +2576,7 @@ async fn tarball_workspace(
|
||||
}
|
||||
}
|
||||
ScriptLang::Php => "php",
|
||||
ScriptLang::Rust => "rs",
|
||||
};
|
||||
archive
|
||||
.write_to_archive(&script.content, &format!("{}.{}", script.path, ext))
|
||||
|
||||
@@ -43,6 +43,7 @@ pub enum ScriptLang {
|
||||
Graphql,
|
||||
Mssql,
|
||||
Php,
|
||||
Rust,
|
||||
}
|
||||
|
||||
impl ScriptLang {
|
||||
@@ -63,6 +64,7 @@ impl ScriptLang {
|
||||
ScriptLang::Mssql => "mssql",
|
||||
ScriptLang::Graphql => "graphql",
|
||||
ScriptLang::Php => "php",
|
||||
ScriptLang::Rust => "rust"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ lazy_static::lazy_static! {
|
||||
"mssql".to_string(),
|
||||
"graphql".to_string(),
|
||||
"php".to_string(),
|
||||
"rust".to_string(),
|
||||
"dependency".to_string(),
|
||||
"flow".to_string(),
|
||||
"other".to_string()
|
||||
|
||||
@@ -26,6 +26,7 @@ windmill-common = { workspace = true, default-features = false }
|
||||
windmill-parser.workspace = true
|
||||
windmill-parser-ts.workspace = true
|
||||
windmill-parser-go.workspace = true
|
||||
windmill-parser-rust.workspace = true
|
||||
windmill-parser-py.workspace = true
|
||||
windmill-parser-py-imports.workspace = true
|
||||
windmill-parser-bash.workspace = true
|
||||
@@ -95,4 +96,4 @@ deno_console.workspace = true
|
||||
deno_url.workspace = true
|
||||
deno_core.workspace = true
|
||||
deno_net.workspace = true
|
||||
zstd.workspace = true
|
||||
zstd.workspace = true
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
name: "rust run script"
|
||||
|
||||
mode: ONCE
|
||||
hostname: "rust"
|
||||
log_level: ERROR
|
||||
|
||||
disable_rl: true
|
||||
|
||||
cwd: "/tmp"
|
||||
|
||||
clone_newnet: false
|
||||
clone_newuser: {CLONE_NEWUSER}
|
||||
|
||||
keep_caps: false
|
||||
keep_env: true
|
||||
|
||||
mount {
|
||||
src: "/bin"
|
||||
dst: "/bin"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "/lib"
|
||||
dst: "/lib"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
|
||||
mount {
|
||||
src: "/lib64"
|
||||
dst: "/lib64"
|
||||
is_bind: true
|
||||
mandatory: false
|
||||
}
|
||||
|
||||
|
||||
mount {
|
||||
src: "/usr"
|
||||
dst: "/usr"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "/dev/null"
|
||||
dst: "/dev/null"
|
||||
is_bind: true
|
||||
rw: true
|
||||
}
|
||||
|
||||
mount {
|
||||
dst: "/tmp"
|
||||
fstype: "tmpfs"
|
||||
rw: true
|
||||
options: "size=500000000"
|
||||
}
|
||||
|
||||
|
||||
mount {
|
||||
src: "{JOB_DIR}/main"
|
||||
dst: "/tmp/main"
|
||||
is_bind: true
|
||||
mandatory: false
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "{JOB_DIR}/args.json"
|
||||
dst: "/tmp/args.json"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "{JOB_DIR}/result.json"
|
||||
dst: "/tmp/result.json"
|
||||
rw: true
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "/etc"
|
||||
dst: "/etc"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "/dev/random"
|
||||
dst: "/dev/random"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
mount {
|
||||
src: "/dev/urandom"
|
||||
dst: "/dev/urandom"
|
||||
is_bind: true
|
||||
}
|
||||
|
||||
iface_no_lo: true
|
||||
|
||||
mount {
|
||||
src: "{CACHE_DIR}"
|
||||
dst: "/tmp/.cache/rust"
|
||||
is_bind: true
|
||||
rw: true
|
||||
mandatory: false
|
||||
}
|
||||
|
||||
{SHARED_MOUNT}
|
||||
@@ -20,6 +20,7 @@ mod mysql_executor;
|
||||
mod pg_executor;
|
||||
mod php_executor;
|
||||
mod python_executor;
|
||||
mod rust_executor;
|
||||
mod worker;
|
||||
mod worker_flow;
|
||||
mod worker_lockfiles;
|
||||
|
||||
@@ -2,7 +2,7 @@ use convert_case::{Case, Casing};
|
||||
use itertools::Itertools;
|
||||
use regex::Regex;
|
||||
use serde_json::value::RawValue;
|
||||
use std::{collections::HashMap, process::Stdio};
|
||||
use std::{collections::HashMap, path::Path, process::Stdio};
|
||||
use tokio::{fs::File, io::AsyncReadExt, process::Command};
|
||||
use uuid::Uuid;
|
||||
use windmill_common::{
|
||||
@@ -71,6 +71,8 @@ pub async fn composer_install(
|
||||
requirements: String,
|
||||
lock: Option<String>,
|
||||
) -> Result<String> {
|
||||
check_php_exists()?;
|
||||
|
||||
write_file(job_dir, "composer.json", &requirements)?;
|
||||
|
||||
if let Some(lock) = lock.as_ref() {
|
||||
@@ -126,6 +128,24 @@ $args->{arg_name} = new {rt_name}($args->{arg_name});"
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
fn check_php_exists() -> error::Result<()> {
|
||||
if !Path::new(PHP_PATH.as_str()).exists() {
|
||||
let msg = format!("Couldn't find php at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full` for your instance in order to run php jobs.", PHP_PATH.as_str());
|
||||
return Err(error::Error::NotFound(msg));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
fn check_php_exists() -> error::Result<()> {
|
||||
if !Path::new(PHP_PATH.as_str()).exists() {
|
||||
let msg = format!("Couldn't find php at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full-ee` for your instance in order to run php jobs.", PHP_PATH.as_str());
|
||||
return Err(error::Error::NotFound(msg));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub async fn handle_php_job(
|
||||
requirements_o: Option<String>,
|
||||
@@ -141,6 +161,8 @@ pub async fn handle_php_job(
|
||||
envs: HashMap<String, String>,
|
||||
shared_mount: &str,
|
||||
) -> error::Result<Box<RawValue>> {
|
||||
check_php_exists()?;
|
||||
|
||||
let (composer_json, composer_lock) = match requirements_o {
|
||||
Some(reqs_and_lock) if !reqs_and_lock.is_empty() => {
|
||||
let splitted = reqs_and_lock.split(COMPOSER_LOCK_SPLIT).collect_vec();
|
||||
|
||||
@@ -0,0 +1,373 @@
|
||||
use serde_json::value::RawValue;
|
||||
use std::{collections::HashMap, path::Path, process::Stdio};
|
||||
use uuid::Uuid;
|
||||
use windmill_parser_rust::parse_rust_deps_into_manifest;
|
||||
|
||||
use itertools::Itertools;
|
||||
use tokio::{fs::File, io::AsyncReadExt, process::Command};
|
||||
use windmill_common::{
|
||||
error::{self, Error},
|
||||
jobs::QueuedJob,
|
||||
utils::calculate_hash,
|
||||
worker::{save_cache, write_file},
|
||||
};
|
||||
use windmill_queue::{append_logs, CanceledBy};
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
create_args_and_out_file, get_reserved_variables, handle_child, read_result,
|
||||
start_child_process,
|
||||
},
|
||||
AuthedClientBackgroundTask, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV,
|
||||
RUST_CACHE_DIR, TZ_ENV,
|
||||
};
|
||||
|
||||
const NSJAIL_CONFIG_RUN_RUST_CONTENT: &str = include_str!("../nsjail/run.rust.config.proto");
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref CARGO_HOME: String = std::env::var("CARGO_HOME").unwrap_or_else(|_| "/usr/local/cargo".to_string());
|
||||
static ref RUSTUP_HOME: String = std::env::var("RUSTUP_HOME").unwrap_or_else(|_| "/usr/local/rustup".to_string());
|
||||
static ref CARGO_PATH: String = format!("{}/bin/cargo", std::env::var("CARGO_HOME").unwrap_or("/usr/local/cargo/bin/cargo".to_string()));
|
||||
}
|
||||
|
||||
const RUST_OBJECT_STORE_PREFIX: &str = "rustbin/";
|
||||
|
||||
fn gen_cargo_crate(code: &str, job_dir: &str) -> anyhow::Result<()> {
|
||||
let manifest = parse_rust_deps_into_manifest(code)?;
|
||||
write_file(job_dir, "Cargo.toml", &manifest)?;
|
||||
|
||||
const WRAPPER_CONTENT: &str = r#"
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Write};
|
||||
use std::error::Error;
|
||||
mod inner;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let args_file = File::open("args.json")?;
|
||||
let reader = BufReader::new(args_file);
|
||||
let args: inner::__WINDMILL_ARGS__ = serde_json::from_reader(reader)?;
|
||||
|
||||
let result = inner::__WINDMILL_RUN__(args)?;
|
||||
|
||||
let mut result_file = File::create("result.json")?;
|
||||
result_file.write_all(result.into_bytes().as_ref())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
"#;
|
||||
write_file(job_dir, "main.rs", WRAPPER_CONTENT)?;
|
||||
|
||||
let sig = windmill_parser_rust::parse_rust_signature(code)?;
|
||||
let spread = &sig
|
||||
.args
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|x| format!("_args.{}", &x.name))
|
||||
.join(", ");
|
||||
let arg_struct_body = &sig
|
||||
.args
|
||||
.into_iter()
|
||||
.map(|x| {
|
||||
format!(
|
||||
"{}: {},",
|
||||
&x.name,
|
||||
windmill_parser_rust::otyp_to_string(x.otyp),
|
||||
)
|
||||
})
|
||||
.join("\n");
|
||||
let mod_content: String = format!(
|
||||
r#"
|
||||
{code}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct __WINDMILL_ARGS__ {{
|
||||
{arg_struct_body}
|
||||
}}
|
||||
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn __WINDMILL_RUN__(_args: __WINDMILL_ARGS__) -> Result<String, Box<dyn std::error::Error>> {{
|
||||
fn windmill_runner<T, E>(f: impl core::ops::FnOnce() -> core::result::Result<T,E>) -> Result<String, E>
|
||||
where
|
||||
T: serde::Serialize,
|
||||
E: std::fmt::Display,
|
||||
{{
|
||||
Ok(serde_json::to_string(&f()?).unwrap())
|
||||
}}
|
||||
Ok(windmill_runner(|| main({spread}))?)
|
||||
}}
|
||||
"#
|
||||
);
|
||||
|
||||
write_file(job_dir, "inner.rs", &mod_content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn generate_cargo_lockfile(
|
||||
job_id: &Uuid,
|
||||
code: &str,
|
||||
mem_peak: &mut i32,
|
||||
canceled_by: &mut Option<CanceledBy>,
|
||||
job_dir: &str,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
worker_name: &str,
|
||||
w_id: &str,
|
||||
) -> error::Result<String> {
|
||||
check_cargo_exists()?;
|
||||
|
||||
gen_cargo_crate(code, job_dir)?;
|
||||
|
||||
let mut gen_lockfile_cmd = Command::new(CARGO_PATH.as_str());
|
||||
gen_lockfile_cmd
|
||||
.current_dir(job_dir)
|
||||
.args(vec!["generate-lockfile"])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
let gen_lockfile_process = start_child_process(gen_lockfile_cmd, CARGO_PATH.as_str()).await?;
|
||||
handle_child(
|
||||
job_id,
|
||||
db,
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
gen_lockfile_process,
|
||||
false,
|
||||
worker_name,
|
||||
w_id,
|
||||
"cargo generate-lockfile",
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let path_lock = format!("{job_dir}/Cargo.lock");
|
||||
let mut file = File::open(path_lock).await?;
|
||||
let mut req_content = String::new();
|
||||
file.read_to_string(&mut req_content).await?;
|
||||
Ok(req_content)
|
||||
}
|
||||
|
||||
pub async fn build_rust_crate(
|
||||
job_id: &Uuid,
|
||||
mem_peak: &mut i32,
|
||||
canceled_by: &mut Option<CanceledBy>,
|
||||
job_dir: &str,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
worker_name: &str,
|
||||
w_id: &str,
|
||||
base_internal_url: &str,
|
||||
hash: &str,
|
||||
) -> error::Result<String> {
|
||||
let bin_path = format!("{}/{hash}", RUST_CACHE_DIR);
|
||||
|
||||
let mut build_rust_cmd = Command::new(CARGO_PATH.as_str());
|
||||
build_rust_cmd
|
||||
.current_dir(job_dir)
|
||||
.env_clear()
|
||||
.env("PATH", PATH_ENV.as_str())
|
||||
.env("BASE_INTERNAL_URL", base_internal_url)
|
||||
.env("HOME", HOME_ENV.as_str())
|
||||
.env("CARGO_HOME", CARGO_HOME.as_str())
|
||||
.env("RUSTUP_HOME", RUSTUP_HOME.as_str())
|
||||
.args(vec!["build", "--release"])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
let build_rust_process = start_child_process(build_rust_cmd, CARGO_PATH.as_str()).await?;
|
||||
handle_child(
|
||||
job_id,
|
||||
db,
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
build_rust_process,
|
||||
false,
|
||||
worker_name,
|
||||
w_id,
|
||||
"rust build",
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
append_logs(job_id, w_id, "\n\n", db).await;
|
||||
|
||||
tokio::fs::copy(
|
||||
&format!("{job_dir}/target/release/main"),
|
||||
format! {"{job_dir}/main"},
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::ExecutionErr(format!(
|
||||
"could not copy built binary from [...]/target/release/main to {job_dir}/main: {e:?}"
|
||||
))
|
||||
})?;
|
||||
|
||||
match save_cache(
|
||||
&bin_path,
|
||||
&format!("{RUST_OBJECT_STORE_PREFIX}{hash}"),
|
||||
&format!("{job_dir}/main"),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(e) => {
|
||||
let em = format!(
|
||||
"could not save {bin_path} to {} to rust cache: {e:?}",
|
||||
format!("{job_dir}/main"),
|
||||
);
|
||||
tracing::error!(em);
|
||||
Ok(em)
|
||||
}
|
||||
Ok(logs) => Ok(logs),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_rust_hash(code: &str, requirements_o: Option<&String>) -> String {
|
||||
calculate_hash(&format!(
|
||||
"{}{}",
|
||||
code,
|
||||
requirements_o
|
||||
.as_ref()
|
||||
.map(|x| x.to_string())
|
||||
.unwrap_or_default()
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
fn check_cargo_exists() -> Result<(), Error> {
|
||||
if !Path::new(CARGO_PATH.as_str()).exists() {
|
||||
let msg = format!("Couldn't find cargo at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full` for your instance in order to run rust jobs.", CARGO_PATH.as_str());
|
||||
return Err(Error::NotFound(msg));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
fn check_cargo_exists() -> Result<(), Error> {
|
||||
if !Path::new(CARGO_PATH.as_str()).exists() {
|
||||
let msg = format!("Couldn't find cargo at {}. This probably means that you are not using the windmill-full image. Please use the image `windmill-full-ee` for your instance in order to run rust jobs.", CARGO_PATH.as_str());
|
||||
return Err(Error::NotFound(msg));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
pub async fn handle_rust_job(
|
||||
mem_peak: &mut i32,
|
||||
canceled_by: &mut Option<CanceledBy>,
|
||||
job: &QueuedJob,
|
||||
db: &sqlx::Pool<sqlx::Postgres>,
|
||||
client: &AuthedClientBackgroundTask,
|
||||
inner_content: &str,
|
||||
job_dir: &str,
|
||||
requirements_o: Option<String>,
|
||||
shared_mount: &str,
|
||||
base_internal_url: &str,
|
||||
worker_name: &str,
|
||||
envs: HashMap<String, String>,
|
||||
) -> Result<Box<RawValue>, Error> {
|
||||
check_cargo_exists()?;
|
||||
|
||||
let hash = compute_rust_hash(inner_content, requirements_o.as_ref());
|
||||
let bin_path = format!("{}/{hash}", RUST_CACHE_DIR);
|
||||
let remote_path = format!("{RUST_OBJECT_STORE_PREFIX}{hash}");
|
||||
|
||||
let (cache, cache_logs) = windmill_common::worker::load_cache(&bin_path, &remote_path).await;
|
||||
|
||||
let cache_logs = if cache {
|
||||
let target = format!("{job_dir}/main");
|
||||
std::os::unix::fs::symlink(&bin_path, &target).map_err(|e| {
|
||||
Error::ExecutionErr(format!(
|
||||
"could not copy cached binary from {bin_path} to {job_dir}/main: {e:?}"
|
||||
))
|
||||
})?;
|
||||
|
||||
create_args_and_out_file(client, job, job_dir, db).await?;
|
||||
cache_logs
|
||||
} else {
|
||||
let logs1 = format!("{cache_logs}\n\n--- CARGO BUILD ---\n");
|
||||
append_logs(&job.id, &job.workspace_id, logs1, db).await;
|
||||
|
||||
gen_cargo_crate(inner_content, job_dir)?;
|
||||
|
||||
if let Some(reqs) = requirements_o {
|
||||
if !reqs.is_empty() {
|
||||
write_file(job_dir, "Cargo.lock", &reqs)?;
|
||||
}
|
||||
}
|
||||
|
||||
create_args_and_out_file(client, job, job_dir, db).await?;
|
||||
|
||||
build_rust_crate(
|
||||
&job.id,
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
job_dir,
|
||||
db,
|
||||
worker_name,
|
||||
&job.workspace_id,
|
||||
base_internal_url,
|
||||
&hash,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
let logs2 = format!("{cache_logs}\n\n--- RUST CODE EXECUTION ---\n");
|
||||
append_logs(&job.id, &job.workspace_id, logs2, db).await;
|
||||
|
||||
let client = &client.get_authed().await;
|
||||
let reserved_variables = get_reserved_variables(job, &client.token, db).await?;
|
||||
|
||||
let child = if !*DISABLE_NSJAIL {
|
||||
let _ = write_file(
|
||||
job_dir,
|
||||
"run.config.proto",
|
||||
&NSJAIL_CONFIG_RUN_RUST_CONTENT
|
||||
.replace("{JOB_DIR}", job_dir)
|
||||
.replace("{CACHE_DIR}", RUST_CACHE_DIR)
|
||||
.replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string())
|
||||
.replace("{SHARED_MOUNT}", shared_mount),
|
||||
)?;
|
||||
let mut nsjail_cmd = Command::new(NSJAIL_PATH.as_str());
|
||||
nsjail_cmd
|
||||
.current_dir(job_dir)
|
||||
.env_clear()
|
||||
.envs(envs)
|
||||
.envs(reserved_variables)
|
||||
.env("PATH", PATH_ENV.as_str())
|
||||
.env("TZ", TZ_ENV.as_str())
|
||||
.env("BASE_INTERNAL_URL", base_internal_url)
|
||||
.args(vec!["--config", "run.config.proto", "--", "/tmp/main"])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await?
|
||||
} else {
|
||||
let compiled_executable_name = "./main";
|
||||
let mut run_rust = Command::new(compiled_executable_name);
|
||||
run_rust
|
||||
.current_dir(job_dir)
|
||||
.env_clear()
|
||||
.envs(envs)
|
||||
.envs(reserved_variables)
|
||||
.env("PATH", PATH_ENV.as_str())
|
||||
.env("TZ", TZ_ENV.as_str())
|
||||
.env("BASE_INTERNAL_URL", base_internal_url)
|
||||
.env("HOME", HOME_ENV.as_str())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
start_child_process(run_rust, compiled_executable_name).await?
|
||||
};
|
||||
handle_child(
|
||||
&job.id,
|
||||
db,
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
child,
|
||||
!*DISABLE_NSJAIL,
|
||||
worker_name,
|
||||
&job.workspace_id,
|
||||
"rust run",
|
||||
job.timeout,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
read_result(job_dir).await
|
||||
}
|
||||
@@ -82,9 +82,7 @@ use rand::Rng;
|
||||
use windmill_queue::{add_completed_job, add_completed_job_error};
|
||||
|
||||
use crate::{
|
||||
bash_executor::{handle_bash_job, handle_powershell_job, ANSI_ESCAPE_RE},
|
||||
bun_executor::handle_bun_job,
|
||||
common::{
|
||||
bash_executor::{handle_bash_job, handle_powershell_job, ANSI_ESCAPE_RE}, bun_executor::handle_bun_job, common::{
|
||||
build_args_map, get_cached_resource_value_if_valid, get_reserved_variables, hash_args,
|
||||
read_result, save_in_cache, NO_LOGS_AT_ALL, SLOW_LOGS,
|
||||
},
|
||||
@@ -94,14 +92,14 @@ use crate::{
|
||||
js_eval::{eval_fetch_timeout, transpile_ts},
|
||||
mysql_executor::do_mysql,
|
||||
pg_executor::do_postgresql,
|
||||
rust_executor::handle_rust_job,
|
||||
php_executor::handle_php_job,
|
||||
python_executor::handle_python_job,
|
||||
worker_flow::{
|
||||
handle_flow, update_flow_status_after_job_completion, update_flow_status_in_progress,
|
||||
},
|
||||
worker_lockfiles::{
|
||||
}, worker_lockfiles::{
|
||||
handle_app_dependency_job, handle_dependency_job, handle_flow_dependency_job,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
@@ -234,6 +232,7 @@ pub const DENO_CACHE_DIR_DEPS: &str = concatcp!(ROOT_CACHE_DIR, "deno/deps");
|
||||
pub const DENO_CACHE_DIR_NPM: &str = concatcp!(ROOT_CACHE_DIR, "deno/npm");
|
||||
|
||||
pub const GO_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "go");
|
||||
pub const RUST_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "rust");
|
||||
pub const BUN_CACHE_DIR: &str = concatcp!(ROOT_CACHE_NOMOUNT_DIR, "bun");
|
||||
pub const BUN_BUNDLE_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "bun");
|
||||
pub const BUN_DEPSTAR_CACHE_DIR: &str = concatcp!(ROOT_CACHE_NOMOUNT_DIR, "buntar");
|
||||
@@ -3153,6 +3152,23 @@ mount {{
|
||||
&shared_mount,
|
||||
)
|
||||
.await
|
||||
},
|
||||
Some(ScriptLang::Rust) => {
|
||||
handle_rust_job(
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
job,
|
||||
db,
|
||||
client,
|
||||
&inner_content,
|
||||
job_dir,
|
||||
requirements_o,
|
||||
&shared_mount,
|
||||
base_internal_url,
|
||||
worker_name,
|
||||
envs,
|
||||
)
|
||||
.await
|
||||
}
|
||||
_ => panic!("unreachable, language is not supported: {language:#?}"),
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ use windmill_parser_ts::parse_expr_for_imports;
|
||||
use windmill_queue::{append_logs, CanceledBy, PushIsolationLevel};
|
||||
|
||||
use crate::python_executor::{create_dependencies_dir, handle_python_reqs, pip_compile};
|
||||
use crate::rust_executor::{build_rust_crate, compute_rust_hash, generate_cargo_lockfile};
|
||||
use crate::{
|
||||
bun_executor::gen_lockfile,
|
||||
deno_executor::generate_deno_lock,
|
||||
@@ -1405,6 +1406,39 @@ async fn capture_dependency_job(
|
||||
)
|
||||
.await
|
||||
}
|
||||
ScriptLang::Rust => {
|
||||
if raw_deps {
|
||||
return Err(Error::ExecutionErr(
|
||||
"Raw dependencies not supported for rust".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let lockfile = generate_cargo_lockfile(
|
||||
job_id,
|
||||
job_raw_code,
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
job_dir,
|
||||
db,
|
||||
worker_name,
|
||||
w_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
build_rust_crate(
|
||||
job_id,
|
||||
mem_peak,
|
||||
canceled_by,
|
||||
job_dir,
|
||||
db,
|
||||
worker_name,
|
||||
w_id,
|
||||
base_internal_url,
|
||||
&compute_rust_hash(&job_raw_code, Some(&lockfile)),
|
||||
)
|
||||
.await?;
|
||||
Ok(lockfile)
|
||||
}
|
||||
ScriptLang::Postgresql => Ok("".to_owned()),
|
||||
ScriptLang::Mysql => Ok("".to_owned()),
|
||||
ScriptLang::Bigquery => Ok("".to_owned()),
|
||||
|
||||
@@ -25,4 +25,7 @@ FROM ghcr.io/windmill-labs/windmill-ee:dev
|
||||
|
||||
RUN apt-get update && apt-get install -y libprotobuf-dev libnl-route-3-dev
|
||||
|
||||
COPY --from=rust:1.80.1 /usr/local/cargo /usr/local/cargo
|
||||
COPY --from=rust:1.80.1 /usr/local/rustup /usr/local/rustup
|
||||
|
||||
COPY --from=nsjail /nsjail/nsjail /bin/nsjail
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
FROM ghcr.io/windmill-labs/windmill-ee:dev
|
||||
|
||||
COPY --from=rust:1.80.1 /usr/local/cargo /usr/local/cargo
|
||||
COPY --from=rust:1.80.1 /usr/local/rustup /usr/local/rustup
|
||||
@@ -0,0 +1,4 @@
|
||||
FROM ghcr.io/windmill-labs/windmill-ee:dev
|
||||
|
||||
COPY --from=rust:1.80.1 /usr/local/cargo /usr/local/cargo
|
||||
COPY --from=rust:1.80.1 /usr/local/rustup /usr/local/rustup
|
||||
@@ -15,7 +15,7 @@ ENV TZ=Etc/UTC
|
||||
|
||||
RUN /usr/local/bin/python3 -m pip install pip-tools
|
||||
|
||||
COPY --from=oven/bun:1.1.8 /usr/local/bin/bun /usr/bin/bun
|
||||
COPY --from=oven/bun:1.1.26 /usr/local/bin/bun /usr/bin/bun
|
||||
|
||||
# add the docker client to call docker from a worker if enabled
|
||||
COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
|
||||
@@ -23,7 +23,7 @@ COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
|
||||
WORKDIR ${APP}
|
||||
|
||||
COPY --from=ghcr.io/windmill-labs/windmill-ee:dev --chmod=755 ${APP}/windmill ${APP}/windmill
|
||||
COPY --from=denoland/deno:1.44.4 --chmod=755 /usr/bin/deno /usr/bin/deno
|
||||
COPY --from=denoland/deno:1.46.1 --chmod=755 /usr/bin/deno /usr/bin/deno
|
||||
|
||||
RUN ln -s ${APP}/windmill /usr/local/bin/windmill
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
ARG PYTHON_IMAGE=python:3.11.8-slim-bookworm
|
||||
|
||||
FROM ${PYTHON_IMAGE}
|
||||
|
||||
ARG APP=/usr/src/app
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y ca-certificates wget curl git jq unzip build-essential unixodbc xmlsec1 software-properties-common \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN curl -sL https://deb.nodesource.com/setup_20.x | bash -
|
||||
RUN apt-get -y update && apt-get install -y curl nodejs awscli
|
||||
|
||||
ENV TZ=Etc/UTC
|
||||
|
||||
RUN /usr/local/bin/python3 -m pip install pip-tools
|
||||
|
||||
COPY --from=oven/bun:1.1.26 /usr/local/bin/bun /usr/bin/bun
|
||||
|
||||
# add the docker client to call docker from a worker if enabled
|
||||
COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
|
||||
|
||||
WORKDIR ${APP}
|
||||
|
||||
COPY --from=ghcr.io/windmill-labs/windmill-ee:dev --chmod=755 ${APP}/windmill ${APP}/windmill
|
||||
COPY --from=denoland/deno:1.46.1 --chmod=755 /usr/bin/deno /usr/bin/deno
|
||||
|
||||
RUN ln -s ${APP}/windmill /usr/local/bin/windmill
|
||||
|
||||
COPY ./frontend/src/lib/hubPaths.json ${APP}/hubPaths.json
|
||||
|
||||
RUN windmill cache ${APP}/hubPaths.json
|
||||
|
||||
RUN rm ${APP}/hubPaths.json
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["windmill"]
|
||||
Generated
+6
@@ -70,6 +70,7 @@
|
||||
"windmill-parser-wasm-php": "^1.382.2",
|
||||
"windmill-parser-wasm-py": "^1.382.2",
|
||||
"windmill-parser-wasm-regex": "^1.382.2",
|
||||
"windmill-parser-wasm-rust": "^1.387.0",
|
||||
"windmill-parser-wasm-ts": "^1.382.2",
|
||||
"windmill-sql-datatype-parser-wasm": "^1.318.0",
|
||||
"y-monaco": "^0.1.4",
|
||||
@@ -13426,6 +13427,11 @@
|
||||
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-regex/-/windmill-parser-wasm-regex-1.385.0.tgz",
|
||||
"integrity": "sha512-rgqTANyiYNgdh2Hcgk8xFt/rmLCMGMnIlnZe+/9z2oggfkOKB9oAt49cyCLD9MZeXdTJuNPKn+IVMeSAOn6WYQ=="
|
||||
},
|
||||
"node_modules/windmill-parser-wasm-rust": {
|
||||
"version": "1.387.0",
|
||||
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-rust/-/windmill-parser-wasm-rust-1.387.0.tgz",
|
||||
"integrity": "sha512-PX0Y/sdaaCkc9rrXB6m+AntGq/RX21fYhdpC7MFCMKb9VakkDAGnFo435xhAi74yLarGd03odUxGymokcBfU6g=="
|
||||
},
|
||||
"node_modules/windmill-parser-wasm-ts": {
|
||||
"version": "1.385.0",
|
||||
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-ts/-/windmill-parser-wasm-ts-1.385.0.tgz",
|
||||
|
||||
@@ -139,6 +139,7 @@
|
||||
"vscode-languageclient": "~9.0.1",
|
||||
"vscode-uri": "~3.0.8",
|
||||
"vscode-ws-jsonrpc": "~3.3.2",
|
||||
"windmill-parser-wasm-rust": "^1.387.0",
|
||||
"windmill-parser-wasm-php": "^1.382.2",
|
||||
"windmill-parser-wasm-go": "^1.382.2",
|
||||
"windmill-parser-wasm-py": "^1.382.2",
|
||||
|
||||
@@ -188,6 +188,7 @@
|
||||
| 'php'
|
||||
| 'css'
|
||||
| 'javascript'
|
||||
| 'rust'
|
||||
export let code: string = ''
|
||||
export let cmdEnterAction: (() => void) | undefined = undefined
|
||||
export let formatAction: (() => void) | undefined = undefined
|
||||
|
||||
@@ -90,7 +90,8 @@
|
||||
'bun',
|
||||
'bunnative',
|
||||
'nativets',
|
||||
'php'
|
||||
'php',
|
||||
'rust'
|
||||
].includes(lang ?? '')
|
||||
$: showVarPicker = [
|
||||
'python3',
|
||||
@@ -101,7 +102,8 @@
|
||||
'bun',
|
||||
'bunnative',
|
||||
'nativets',
|
||||
'php'
|
||||
'php',
|
||||
'rust'
|
||||
].includes(lang ?? '')
|
||||
$: showResourcePicker = [
|
||||
'python3',
|
||||
@@ -112,7 +114,8 @@
|
||||
'bun',
|
||||
'bunnative',
|
||||
'nativets',
|
||||
'php'
|
||||
'php',
|
||||
'rust'
|
||||
].includes(lang ?? '')
|
||||
$: showResourceTypePicker =
|
||||
['typescript', 'javascript'].includes(scriptLangToEditorLang(lang)) ||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import sql from 'svelte-highlight/languages/sql'
|
||||
import powershell from 'svelte-highlight/languages/powershell'
|
||||
import php from 'svelte-highlight/languages/php'
|
||||
import rust from 'svelte-highlight/languages/rust'
|
||||
import type { Script } from '$lib/gen'
|
||||
import { Button } from './common'
|
||||
import { copyToClipboard } from '$lib/utils'
|
||||
@@ -51,7 +52,8 @@
|
||||
return powershell
|
||||
case 'php':
|
||||
return php
|
||||
|
||||
case 'rust':
|
||||
return rust
|
||||
default:
|
||||
return typescript
|
||||
}
|
||||
|
||||
@@ -112,7 +112,8 @@
|
||||
'flow',
|
||||
'other',
|
||||
'bun',
|
||||
'php'
|
||||
'php',
|
||||
'rust'
|
||||
]
|
||||
const nativeTags = [
|
||||
'nativets',
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import DenoIcon from '$lib/components/icons/DenoIcon.svelte'
|
||||
import type { Script } from '$lib/gen'
|
||||
import PHPIcon from '$lib/components/icons/PHPIcon.svelte'
|
||||
import RustIcon from '$lib/components/icons/RustIcon.svelte'
|
||||
|
||||
export let lang:
|
||||
| SupportedLanguage
|
||||
@@ -46,7 +47,8 @@
|
||||
mysql: 'MySQL',
|
||||
mssql: 'MS SQL Server',
|
||||
bun: 'TypeScript',
|
||||
php: 'PHP'
|
||||
php: 'PHP',
|
||||
rust: 'Rust'
|
||||
}
|
||||
|
||||
const langToComponent: Record<
|
||||
@@ -72,7 +74,8 @@
|
||||
postgresql: PostgresIcon,
|
||||
nativets: RestIcon,
|
||||
graphql: GraphqlIcon,
|
||||
php: PHPIcon
|
||||
php: PHPIcon,
|
||||
rust: RustIcon
|
||||
}
|
||||
|
||||
let subIconScale = width === 30 ? 0.6 : 0.8
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<script>
|
||||
export let height = '24px'
|
||||
export let width = '24px'
|
||||
</script>
|
||||
|
||||
<svg
|
||||
{height}
|
||||
{width}
|
||||
id="svg3430"
|
||||
version="1.1"
|
||||
viewBox="0 0 106 106"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
|
||||
<g id="logo" transform="translate(53, 53)">
|
||||
<path id="r" transform="translate(0.5, 0.5)" stroke="black" stroke-width="1" stroke-linejoin="round" d=" M -9,-15 H 4 C 12,-15 12,-7 4,-7 H -9 Z M -40,22 H 0 V 11 H -9 V 3 H 1 C 12,3 6,22 15,22 H 40 V 3 H 34 V 5 C 34,13 25,12 24,7 C 23,2 19,-2 18,-2 C 33,-10 24,-26 12,-26 H -35 V -15 H -25 V 11 H -40 Z"/>
|
||||
<g id="gear" mask="url(#holes)">
|
||||
<circle r="43" fill="none" stroke="black" stroke-width="9"/>
|
||||
<g id="cogs">
|
||||
<polygon id="cog" stroke="black" stroke-width="3" stroke-linejoin="round" points="46,3 51,0 46,-3"/>
|
||||
<use xlink:href="#cog" transform="rotate(11.25)"/>
|
||||
<use xlink:href="#cog" transform="rotate(22.50)"/>
|
||||
<use xlink:href="#cog" transform="rotate(33.75)"/>
|
||||
<use xlink:href="#cog" transform="rotate(45.00)"/>
|
||||
<use xlink:href="#cog" transform="rotate(56.25)"/>
|
||||
<use xlink:href="#cog" transform="rotate(67.50)"/>
|
||||
<use xlink:href="#cog" transform="rotate(78.75)"/>
|
||||
<use xlink:href="#cog" transform="rotate(90.00)"/>
|
||||
<use xlink:href="#cog" transform="rotate(101.25)"/>
|
||||
<use xlink:href="#cog" transform="rotate(112.50)"/>
|
||||
<use xlink:href="#cog" transform="rotate(123.75)"/>
|
||||
<use xlink:href="#cog" transform="rotate(135.00)"/>
|
||||
<use xlink:href="#cog" transform="rotate(146.25)"/>
|
||||
<use xlink:href="#cog" transform="rotate(157.50)"/>
|
||||
<use xlink:href="#cog" transform="rotate(168.75)"/>
|
||||
<use xlink:href="#cog" transform="rotate(180.00)"/>
|
||||
<use xlink:href="#cog" transform="rotate(191.25)"/>
|
||||
<use xlink:href="#cog" transform="rotate(202.50)"/>
|
||||
<use xlink:href="#cog" transform="rotate(213.75)"/>
|
||||
<use xlink:href="#cog" transform="rotate(225.00)"/>
|
||||
<use xlink:href="#cog" transform="rotate(236.25)"/>
|
||||
<use xlink:href="#cog" transform="rotate(247.50)"/>
|
||||
<use xlink:href="#cog" transform="rotate(258.75)"/>
|
||||
<use xlink:href="#cog" transform="rotate(270.00)"/>
|
||||
<use xlink:href="#cog" transform="rotate(281.25)"/>
|
||||
<use xlink:href="#cog" transform="rotate(292.50)"/>
|
||||
<use xlink:href="#cog" transform="rotate(303.75)"/>
|
||||
<use xlink:href="#cog" transform="rotate(315.00)"/>
|
||||
<use xlink:href="#cog" transform="rotate(326.25)"/>
|
||||
<use xlink:href="#cog" transform="rotate(337.50)"/>
|
||||
<use xlink:href="#cog" transform="rotate(348.75)"/>
|
||||
</g>
|
||||
<g id="mounts">
|
||||
<polygon id="mount" stroke="black" stroke-width="6" stroke-linejoin="round" points="-7,-42 0,-35 7,-42"/>
|
||||
<use xlink:href="#mount" transform="rotate(72)"/>
|
||||
<use xlink:href="#mount" transform="rotate(144)"/>
|
||||
<use xlink:href="#mount" transform="rotate(216)"/>
|
||||
<use xlink:href="#mount" transform="rotate(288)"/>
|
||||
</g>
|
||||
</g>
|
||||
<mask id="holes">
|
||||
<rect x="-60" y="-60" width="120" height="120" fill="white"/>
|
||||
<circle id="hole" cy="-40" r="3"/>
|
||||
<use xlink:href="#hole" transform="rotate(72)"/>
|
||||
<use xlink:href="#hole" transform="rotate(144)"/>
|
||||
<use xlink:href="#hole" transform="rotate(216)"/>
|
||||
<use xlink:href="#hole" transform="rotate(288)"/>
|
||||
</mask>
|
||||
</g>
|
||||
</svg>
|
||||
@@ -0,0 +1 @@
|
||||
<svg height="144" width="144" xmlns="http://www.w3.org/2000/svg"><path d="m71.05 23.68c-26.06 0-47.27 21.22-47.27 47.27s21.22 47.27 47.27 47.27 47.27-21.22 47.27-47.27-21.22-47.27-47.27-47.27zm-.07 4.2a3.1 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11zm7.12 5.12a38.27 38.27 0 0 1 26.2 18.66l-3.67 8.28c-.63 1.43.02 3.11 1.44 3.75l7.06 3.13a38.27 38.27 0 0 1 .08 6.64h-3.93c-.39 0-.55.26-.55.64v1.8c0 4.24-2.39 5.17-4.49 5.4-2 .23-4.21-.84-4.49-2.06-1.18-6.63-3.14-8.04-6.24-10.49 3.85-2.44 7.85-6.05 7.85-10.87 0-5.21-3.57-8.49-6-10.1-3.42-2.25-7.2-2.7-8.22-2.7h-40.6a38.27 38.27 0 0 1 21.41-12.08l4.79 5.02c1.08 1.13 2.87 1.18 4 .09zm-44.2 23.02a3.11 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11zm74.15.14a3.11 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11zm-68.29.5h5.42v24.44h-10.94a38.27 38.27 0 0 1 -1.24-14.61l6.7-2.98c1.43-.64 2.08-2.31 1.44-3.74zm22.62.26h12.91c.67 0 4.71.77 4.71 3.8 0 2.51-3.1 3.41-5.65 3.41h-11.98zm0 17.56h9.89c.9 0 4.83.26 6.08 5.28.39 1.54 1.26 6.56 1.85 8.17.59 1.8 2.98 5.4 5.53 5.4h16.14a38.27 38.27 0 0 1 -3.54 4.1l-6.57-1.41c-1.53-.33-3.04.65-3.37 2.18l-1.56 7.28a38.27 38.27 0 0 1 -31.91-.15l-1.56-7.28c-.33-1.53-1.83-2.51-3.36-2.18l-6.43 1.38a38.27 38.27 0 0 1 -3.32-3.92h31.27c.35 0 .59-.06.59-.39v-11.06c0-.32-.24-.39-.59-.39h-9.15zm-14.43 25.33a3.11 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11zm46.05.14a3.11 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11z"/><path d="m115.68 70.95a44.63 44.63 0 0 1 -44.63 44.63 44.63 44.63 0 0 1 -44.63-44.63 44.63 44.63 0 0 1 44.63-44.63 44.63 44.63 0 0 1 44.63 44.63zm-.84-4.31 6.96 4.31-6.96 4.31 5.98 5.59-7.66 2.87 4.78 6.65-8.09 1.32 3.4 7.46-8.19-.29 1.88 7.98-7.98-1.88.29 8.19-7.46-3.4-1.32 8.09-6.65-4.78-2.87 7.66-5.59-5.98-4.31 6.96-4.31-6.96-5.59 5.98-2.87-7.66-6.65 4.78-1.32-8.09-7.46 3.4.29-8.19-7.98 1.88 1.88-7.98-8.19.29 3.4-7.46-8.09-1.32 4.78-6.65-7.66-2.87 5.98-5.59-6.96-4.31 6.96-4.31-5.98-5.59 7.66-2.87-4.78-6.65 8.09-1.32-3.4-7.46 8.19.29-1.88-7.98 7.98 1.88-.29-8.19 7.46 3.4 1.32-8.09 6.65 4.78 2.87-7.66 5.59 5.98 4.31-6.96 4.31 6.96 5.59-5.98 2.87 7.66 6.65-4.78 1.32 8.09 7.46-3.4-.29 8.19 7.98-1.88-1.88 7.98 8.19-.29-3.4 7.46 8.09 1.32-4.78 6.65 7.66 2.87z" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/></svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -67,6 +67,8 @@ export function langToExt(lang: string): string {
|
||||
return 'ps1'
|
||||
case 'php':
|
||||
return 'php'
|
||||
case 'rust':
|
||||
return 'rs'
|
||||
case 'deno':
|
||||
return 'ts'
|
||||
case 'nativets':
|
||||
|
||||
@@ -29,12 +29,16 @@ import initGoParser, {
|
||||
import initPhpParser, {
|
||||
parse_php,
|
||||
} from 'windmill-parser-wasm-php'
|
||||
import initRustParser, {
|
||||
parse_rust,
|
||||
} from 'windmill-parser-wasm-rust'
|
||||
|
||||
import wasmUrlTs from 'windmill-parser-wasm-ts/windmill_parser_wasm_bg.wasm?url'
|
||||
import wasmUrlRegex from 'windmill-parser-wasm-regex/windmill_parser_wasm_bg.wasm?url'
|
||||
import wasmUrlPy from 'windmill-parser-wasm-py/windmill_parser_wasm_bg.wasm?url'
|
||||
import wasmUrlGo from 'windmill-parser-wasm-go/windmill_parser_wasm_bg.wasm?url'
|
||||
import wasmUrlPhp from 'windmill-parser-wasm-php/windmill_parser_wasm_bg.wasm?url'
|
||||
import wasmUrlRust from 'windmill-parser-wasm-rust/windmill_parser_wasm_bg.wasm?url'
|
||||
import { workspaceStore } from './stores.js'
|
||||
import { argSigToJsonSchemaType } from './inferArgSig.js'
|
||||
|
||||
@@ -56,6 +60,9 @@ async function initWasmPython() {
|
||||
async function initWasmPhp() {
|
||||
await initPhpParser(wasmUrlPhp)
|
||||
}
|
||||
async function initWasmRust() {
|
||||
await initRustParser(wasmUrlRust)
|
||||
}
|
||||
async function initWasmGo() {
|
||||
await initGoParser(wasmUrlGo)
|
||||
}
|
||||
@@ -160,6 +167,9 @@ export async function inferArgs(
|
||||
} else if (language == 'php') {
|
||||
await initWasmPhp()
|
||||
inferedSchema = JSON.parse(parse_php(code))
|
||||
} else if (language == 'rust') {
|
||||
await initWasmRust()
|
||||
inferedSchema = JSON.parse(parse_rust(code))
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -252,6 +252,43 @@ function main(
|
||||
}
|
||||
`
|
||||
|
||||
export const RUST_INIT_CODE = `//! Add dependencies in the following partial Cargo.toml manifest
|
||||
//!
|
||||
//! \`\`\`cargo
|
||||
//! [dependencies]
|
||||
//! anyhow = "1.0.86"
|
||||
//! rand = "0.7.2"
|
||||
//! \`\`\`
|
||||
//!
|
||||
//! Note that serde is used by default with the \`derive\` feature.
|
||||
//! You can still reimport it if you need additional features.
|
||||
|
||||
use anyhow::anyhow;
|
||||
use rand::seq::SliceRandom;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct Ret {
|
||||
msg: String,
|
||||
number: i8,
|
||||
}
|
||||
|
||||
fn main(who_to_greet: String, numbers: Vec<i8>) -> anyhow::Result<Ret> {
|
||||
println!(
|
||||
"Person to greet: {} - numbers to choose: {:?}",
|
||||
who_to_greet, numbers
|
||||
);
|
||||
Ok(Ret {
|
||||
msg: format!("Greetings {}!", who_to_greet),
|
||||
number: *numbers
|
||||
.choose(&mut rand::thread_rng())
|
||||
.ok_or(anyhow!("There should be some numbers to choose from"))?,
|
||||
})
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
|
||||
export const FETCH_INIT_CODE = `export async function main(
|
||||
url: string | undefined,
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' = 'GET',
|
||||
@@ -465,7 +502,8 @@ const ALL_INITIAL_CODE = [
|
||||
BUN_INIT_CODE_APPROVAL,
|
||||
BASH_INIT_CODE,
|
||||
POWERSHELL_INIT_CODE,
|
||||
PHP_INIT_CODE
|
||||
PHP_INIT_CODE,
|
||||
RUST_INIT_CODE,
|
||||
]
|
||||
|
||||
export function isInitialCode(content: string): boolean {
|
||||
@@ -552,6 +590,8 @@ export function initialCode(
|
||||
return GRAPHQL_INIT_CODE
|
||||
} else if (language == 'php') {
|
||||
return PHP_INIT_CODE
|
||||
} else if (language == 'rust') {
|
||||
return RUST_INIT_CODE
|
||||
} else if (language == 'bun' || language == 'bunnative') {
|
||||
if (language == 'bunnative' || subkind === 'bunnative') {
|
||||
return BUNNATIVE_INIT_CODE
|
||||
|
||||
@@ -33,6 +33,8 @@ export function scriptLangToEditorLang(
|
||||
return 'powershell'
|
||||
} else if (lang == 'php') {
|
||||
return 'php'
|
||||
} else if (lang == 'rust') {
|
||||
return 'rust'
|
||||
} else if (lang == 'graphql') {
|
||||
return 'graphql'
|
||||
} else if (lang == undefined) {
|
||||
@@ -110,7 +112,8 @@ const scriptLanguagesArray: [SupportedLanguage | 'docker' | 'bunnative', string]
|
||||
['graphql', 'GraphQL'],
|
||||
['powershell', 'PowerShell'],
|
||||
['php', 'PHP'],
|
||||
['docker', 'Docker']
|
||||
['rust', 'Rust'],
|
||||
['docker', 'Docker'],
|
||||
]
|
||||
export function processLangs(selected: string | undefined, langs: string[]): string[] {
|
||||
if (selected === 'nativets') {
|
||||
|
||||
@@ -647,7 +647,7 @@
|
||||
<Badge color="blue">priority: {job.priority}</Badge>
|
||||
</div>
|
||||
{/if}
|
||||
{#if job.tag && !['deno', 'python3', 'flow', 'other', 'go', 'postgresql', 'mysql', 'bigquery', 'snowflake', 'mssql', 'graphql', 'nativets', 'bash', 'powershell', 'php', 'other', 'dependency'].includes(job.tag)}
|
||||
{#if job.tag && !['deno', 'python3', 'flow', 'other', 'go', 'postgresql', 'mysql', 'bigquery', 'snowflake', 'mssql', 'graphql', 'nativets', 'bash', 'powershell', 'php', 'rust', 'other', 'dependency'].includes(job.tag)}
|
||||
<div>
|
||||
<Badge color="indigo">Tag: {job.tag}</Badge>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user