@tishlang/tish 1.9.2 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/tish +0 -0
- package/crates/js_to_tish/src/transform/expr.rs +8 -6
- package/crates/js_to_tish/src/transform/stmt.rs +12 -13
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish/src/cargo_native_registry.rs +4 -1
- package/crates/tish/src/cli_help.rs +9 -1
- package/crates/tish/src/main.rs +66 -11
- package/crates/tish/tests/integration_test.rs +145 -7
- package/crates/tish_ast/src/ast.rs +3 -9
- package/crates/tish_build_utils/src/lib.rs +74 -23
- package/crates/tish_builtins/src/array.rs +2 -3
- package/crates/tish_builtins/src/construct.rs +15 -28
- package/crates/tish_builtins/src/globals.rs +18 -16
- package/crates/tish_builtins/src/helpers.rs +1 -4
- package/crates/tish_builtins/src/lib.rs +1 -0
- package/crates/tish_builtins/src/math.rs +7 -0
- package/crates/tish_builtins/src/object.rs +10 -10
- package/crates/tish_builtins/src/string.rs +27 -3
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_compile/src/codegen.rs +324 -158
- package/crates/tish_compile/src/lib.rs +39 -7
- package/crates/tish_compile/src/resolve.rs +191 -6
- package/crates/tish_compile/src/types.rs +6 -6
- package/crates/tish_compile_js/src/codegen.rs +8 -5
- package/crates/tish_core/src/console_style.rs +9 -0
- package/crates/tish_core/src/json.rs +17 -7
- package/crates/tish_core/src/macros.rs +2 -2
- package/crates/tish_core/src/value.rs +213 -4
- package/crates/tish_cranelift/src/link.rs +1 -1
- package/crates/tish_cranelift_runtime/Cargo.toml +4 -0
- package/crates/tish_eval/src/eval.rs +135 -73
- package/crates/tish_eval/src/http.rs +18 -12
- package/crates/tish_eval/src/lib.rs +29 -0
- package/crates/tish_eval/src/regex.rs +1 -1
- package/crates/tish_eval/src/value.rs +89 -4
- package/crates/tish_eval/src/value_convert.rs +30 -8
- package/crates/tish_fmt/src/lib.rs +4 -1
- package/crates/tish_lexer/src/lib.rs +7 -2
- package/crates/tish_llvm/src/lib.rs +2 -2
- package/crates/tish_lsp/src/builtin_goto.rs +111 -10
- package/crates/tish_lsp/src/import_goto.rs +35 -22
- package/crates/tish_lsp/src/main.rs +118 -85
- package/crates/tish_native/src/build.rs +270 -24
- package/crates/tish_native/src/config.rs +48 -0
- package/crates/tish_native/src/lib.rs +139 -12
- package/crates/tish_parser/src/lib.rs +5 -2
- package/crates/tish_parser/src/parser.rs +45 -75
- package/crates/tish_pg/src/error.rs +1 -1
- package/crates/tish_pg/src/lib.rs +61 -73
- package/crates/tish_resolve/src/lib.rs +283 -158
- package/crates/tish_resolve/src/pos.rs +10 -2
- package/crates/tish_runtime/Cargo.toml +3 -0
- package/crates/tish_runtime/src/http.rs +39 -39
- package/crates/tish_runtime/src/http_fetch.rs +12 -12
- package/crates/tish_runtime/src/lib.rs +35 -44
- package/crates/tish_runtime/src/native_promise.rs +0 -11
- package/crates/tish_runtime/src/promise.rs +14 -1
- package/crates/tish_runtime/src/promise_io.rs +1 -4
- package/crates/tish_runtime/src/timers.rs +12 -7
- package/crates/tish_runtime/src/ws.rs +40 -27
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +10 -8
- package/crates/tish_ui/src/jsx.rs +6 -4
- package/crates/tish_ui/src/lib.rs +5 -4
- package/crates/tish_ui/src/runtime/hooks.rs +123 -37
- package/crates/tish_ui/src/runtime/mod.rs +21 -41
- package/crates/tish_vm/Cargo.toml +2 -0
- package/crates/tish_vm/src/vm.rs +258 -153
- package/crates/tish_wasm/src/lib.rs +60 -7
- package/crates/tish_wasm_runtime/Cargo.toml +10 -1
- package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
- package/crates/tish_wasm_runtime/src/lib.rs +7 -1
- package/crates/tishlang_cargo_bindgen/src/classify.rs +1 -3
- package/crates/tishlang_cargo_bindgen/src/discover.rs +10 -5
- package/crates/tishlang_cargo_bindgen/src/infer.rs +18 -8
- package/crates/tishlang_cargo_bindgen/src/lib.rs +25 -26
- package/crates/tishlang_cargo_bindgen/src/main.rs +41 -38
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +4 -1
- package/justfile +3 -3
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
|
@@ -5,9 +5,18 @@ use std::path::Path;
|
|
|
5
5
|
|
|
6
6
|
use tishlang_compile::ResolvedNativeModule;
|
|
7
7
|
|
|
8
|
+
use crate::config::{NativeArtifact, NativeBuildConfig};
|
|
9
|
+
|
|
8
10
|
/// `tishlang_runtime` Cargo feature names (subset of CLI / compile feature names).
|
|
9
|
-
const RUNTIME_CARGO_FEATURES: &[&str] =
|
|
10
|
-
|
|
11
|
+
const RUNTIME_CARGO_FEATURES: &[&str] = &[
|
|
12
|
+
"http",
|
|
13
|
+
"http-hyper",
|
|
14
|
+
"http-io-uring",
|
|
15
|
+
"fs",
|
|
16
|
+
"process",
|
|
17
|
+
"regex",
|
|
18
|
+
"ws",
|
|
19
|
+
];
|
|
11
20
|
|
|
12
21
|
/// Map CLI/compile features to flags passed to `tishlang_runtime` in the temp crate's Cargo.toml.
|
|
13
22
|
/// `full` enables every optional runtime capability (matches `tish build --feature full` / LANGUAGE.md).
|
|
@@ -29,6 +38,29 @@ fn runtime_features_for_cargo(features: &[String]) -> Vec<String> {
|
|
|
29
38
|
out
|
|
30
39
|
}
|
|
31
40
|
|
|
41
|
+
/// `[profile.release]` for nested `cargo build` of generated crates.
|
|
42
|
+
fn nested_release_profile_toml() -> &'static str {
|
|
43
|
+
if std::env::var("TISH_FAST_NATIVE_BUILD").as_deref() == Ok("1") {
|
|
44
|
+
r#"[profile.release]
|
|
45
|
+
opt-level = 1
|
|
46
|
+
lto = false
|
|
47
|
+
codegen-units = 16
|
|
48
|
+
incremental = true
|
|
49
|
+
strip = false
|
|
50
|
+
debug = 0
|
|
51
|
+
panic = "abort"
|
|
52
|
+
"#
|
|
53
|
+
} else {
|
|
54
|
+
r#"[profile.release]
|
|
55
|
+
# Reduce binary size: strip symbols, abort on panic (no unwinding), single codegen unit
|
|
56
|
+
strip = true
|
|
57
|
+
panic = "abort"
|
|
58
|
+
codegen-units = 1
|
|
59
|
+
lto = "fat"
|
|
60
|
+
"#
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
32
64
|
/// Inject `mod generated_native;` after the crate attribute so the binary crate can call `crate::generated_native::…`.
|
|
33
65
|
fn inject_generated_native_mod(rust_code: &str) -> String {
|
|
34
66
|
if let Some(pos) = rust_code.find("\n\n") {
|
|
@@ -39,6 +71,10 @@ fn inject_generated_native_mod(rust_code: &str) -> String {
|
|
|
39
71
|
}
|
|
40
72
|
}
|
|
41
73
|
|
|
74
|
+
pub(crate) fn rust_code_needs_tokio(rust_code: &str) -> bool {
|
|
75
|
+
rust_code.contains("#[tokio::main]") || rust_code.contains("tokio::runtime::Runtime")
|
|
76
|
+
}
|
|
77
|
+
|
|
42
78
|
pub fn build_via_cargo(
|
|
43
79
|
rust_code: &str,
|
|
44
80
|
native_modules: Vec<ResolvedNativeModule>,
|
|
@@ -48,11 +84,34 @@ pub fn build_via_cargo(
|
|
|
48
84
|
generated_native_rs: Option<&str>,
|
|
49
85
|
project_root: Option<&Path>,
|
|
50
86
|
) -> Result<(), String> {
|
|
51
|
-
|
|
87
|
+
build_via_cargo_with_config(
|
|
88
|
+
rust_code,
|
|
89
|
+
native_modules,
|
|
90
|
+
output_path,
|
|
91
|
+
features,
|
|
92
|
+
extra_dependencies_toml,
|
|
93
|
+
generated_native_rs,
|
|
94
|
+
project_root,
|
|
95
|
+
&NativeBuildConfig::desktop(),
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
pub fn build_via_cargo_with_config(
|
|
100
|
+
rust_code: &str,
|
|
101
|
+
native_modules: Vec<ResolvedNativeModule>,
|
|
102
|
+
output_path: &Path,
|
|
103
|
+
features: &[String],
|
|
104
|
+
extra_dependencies_toml: &str,
|
|
105
|
+
generated_native_rs: Option<&str>,
|
|
106
|
+
project_root: Option<&Path>,
|
|
107
|
+
build_config: &NativeBuildConfig,
|
|
108
|
+
) -> Result<(), String> {
|
|
109
|
+
let out_stem = output_path
|
|
52
110
|
.file_stem()
|
|
53
111
|
.and_then(|s| s.to_str())
|
|
54
112
|
.unwrap_or("tish_out");
|
|
55
|
-
let
|
|
113
|
+
let cargo_name = tishlang_build_utils::cargo_target_name(out_stem);
|
|
114
|
+
let build_dir = tishlang_build_utils::create_build_dir("tish_build", out_stem)?;
|
|
56
115
|
|
|
57
116
|
let runtime_path = tishlang_build_utils::find_runtime_path_for_project(project_root)?;
|
|
58
117
|
|
|
@@ -64,7 +123,7 @@ pub fn build_via_cargo(
|
|
|
64
123
|
format!(", features = {:?}", runtime_refs)
|
|
65
124
|
};
|
|
66
125
|
|
|
67
|
-
let needs_tokio = rust_code
|
|
126
|
+
let needs_tokio = rust_code_needs_tokio(rust_code);
|
|
68
127
|
let tokio_dep = if needs_tokio {
|
|
69
128
|
"\ntokio = { version = \"1\", features = [\"rt-multi-thread\", \"macros\"] }\n"
|
|
70
129
|
} else {
|
|
@@ -108,27 +167,43 @@ pub fn build_via_cargo(
|
|
|
108
167
|
String::new()
|
|
109
168
|
};
|
|
110
169
|
|
|
170
|
+
let profile = nested_release_profile_toml();
|
|
171
|
+
let src_file = if build_config.artifact == NativeArtifact::StaticLib {
|
|
172
|
+
"lib.rs"
|
|
173
|
+
} else {
|
|
174
|
+
"main.rs"
|
|
175
|
+
};
|
|
176
|
+
let crate_section = if build_config.artifact == NativeArtifact::StaticLib {
|
|
177
|
+
format!(
|
|
178
|
+
r#"[lib]
|
|
179
|
+
name = "{}"
|
|
180
|
+
crate-type = ["staticlib"]
|
|
181
|
+
path = "src/lib.rs"
|
|
182
|
+
|
|
183
|
+
"#,
|
|
184
|
+
cargo_name
|
|
185
|
+
)
|
|
186
|
+
} else {
|
|
187
|
+
format!(
|
|
188
|
+
r#"[[bin]]
|
|
189
|
+
name = "{}"
|
|
190
|
+
path = "src/main.rs"
|
|
191
|
+
|
|
192
|
+
"#,
|
|
193
|
+
cargo_name
|
|
194
|
+
)
|
|
195
|
+
};
|
|
111
196
|
let cargo_toml = format!(
|
|
112
197
|
r#"[package]
|
|
113
198
|
name = "tish_output"
|
|
114
199
|
version = "0.1.0"
|
|
115
200
|
edition = "2021"
|
|
116
201
|
|
|
117
|
-
|
|
118
|
-
name = "{}"
|
|
119
|
-
path = "src/main.rs"
|
|
120
|
-
|
|
121
|
-
[profile.release]
|
|
122
|
-
# Reduce binary size: strip symbols, abort on panic (no unwinding), single codegen unit
|
|
123
|
-
strip = true
|
|
124
|
-
panic = "abort"
|
|
125
|
-
codegen-units = 1
|
|
126
|
-
lto = "fat"
|
|
127
|
-
|
|
202
|
+
{}{}
|
|
128
203
|
[dependencies]
|
|
129
204
|
tishlang_runtime = {{ path = {:?}{} }}
|
|
130
205
|
{}{}"#,
|
|
131
|
-
|
|
206
|
+
crate_section, profile, runtime_path, features_str, more_deps, ui_dep
|
|
132
207
|
);
|
|
133
208
|
|
|
134
209
|
fs::write(build_dir.join("Cargo.toml"), cargo_toml)
|
|
@@ -137,8 +212,173 @@ tishlang_runtime = {{ path = {:?}{} }}
|
|
|
137
212
|
fs::write(build_dir.join("src/generated_native.rs"), gen)
|
|
138
213
|
.map_err(|e| format!("Cannot write generated_native.rs: {}", e))?;
|
|
139
214
|
}
|
|
140
|
-
fs::write(build_dir.join("src
|
|
141
|
-
.map_err(|e| format!("Cannot write
|
|
215
|
+
fs::write(build_dir.join("src").join(src_file), rust_main)
|
|
216
|
+
.map_err(|e| format!("Cannot write {}: {}", src_file, e))?;
|
|
217
|
+
|
|
218
|
+
let workspace_target = Path::new(&runtime_path)
|
|
219
|
+
.parent()
|
|
220
|
+
.and_then(|p| p.parent())
|
|
221
|
+
.map(|ws| ws.join("target"));
|
|
222
|
+
let target_dir = workspace_target.filter(|p| p.exists());
|
|
223
|
+
let cross = build_config.cargo_target.as_deref();
|
|
224
|
+
let release_sub = if let Some(triple) = cross {
|
|
225
|
+
format!("{triple}/release")
|
|
226
|
+
} else {
|
|
227
|
+
"release".to_string()
|
|
228
|
+
};
|
|
229
|
+
let binary_dir = target_dir
|
|
230
|
+
.as_ref()
|
|
231
|
+
.map(|t| t.join(&release_sub))
|
|
232
|
+
.unwrap_or_else(|| build_dir.join("target").join(&release_sub));
|
|
233
|
+
|
|
234
|
+
tishlang_build_utils::run_cargo_build(&build_dir, target_dir.as_deref(), cross)?;
|
|
235
|
+
|
|
236
|
+
let artifact = if build_config.artifact == NativeArtifact::StaticLib {
|
|
237
|
+
tishlang_build_utils::find_release_staticlib(&binary_dir, &cargo_name)?
|
|
238
|
+
} else {
|
|
239
|
+
tishlang_build_utils::find_release_binary(&binary_dir, &cargo_name)?
|
|
240
|
+
};
|
|
241
|
+
let target = if build_config.artifact == NativeArtifact::StaticLib {
|
|
242
|
+
if output_path.extension().is_some_and(|e| e == "a") {
|
|
243
|
+
output_path.to_path_buf()
|
|
244
|
+
} else if output_path.to_string_lossy().ends_with('/') || output_path.is_dir() {
|
|
245
|
+
output_path.join(format!("lib{out_stem}.a"))
|
|
246
|
+
} else {
|
|
247
|
+
output_path.with_extension("a")
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
tishlang_build_utils::resolve_output_path(output_path, out_stem)
|
|
251
|
+
};
|
|
252
|
+
tishlang_build_utils::copy_binary_to_output(&artifact, &target)?;
|
|
253
|
+
|
|
254
|
+
Ok(())
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/// Build several native binaries in **one** nested Cargo project (shared `tishlang_runtime` compile).
|
|
258
|
+
///
|
|
259
|
+
/// `bins` order must match `outputs`: each `(stem, rust_code, generated_native_rs)` pairs with
|
|
260
|
+
/// `outputs[i].0` (entry path — used only for validation) and `outputs[i].1` (final binary path).
|
|
261
|
+
pub(crate) fn build_many_via_cargo(
|
|
262
|
+
bins: Vec<(String, String, Option<String>)>,
|
|
263
|
+
native_modules: Vec<ResolvedNativeModule>,
|
|
264
|
+
features: &[String],
|
|
265
|
+
extra_dependencies_toml: &str,
|
|
266
|
+
needs_tokio: bool,
|
|
267
|
+
needs_ui: bool,
|
|
268
|
+
outputs: &[(&Path, &Path)],
|
|
269
|
+
project_root: Option<&Path>,
|
|
270
|
+
) -> Result<(), String> {
|
|
271
|
+
if bins.len() != outputs.len() {
|
|
272
|
+
return Err(format!(
|
|
273
|
+
"build_many_via_cargo: bins ({}) != outputs ({})",
|
|
274
|
+
bins.len(),
|
|
275
|
+
outputs.len()
|
|
276
|
+
));
|
|
277
|
+
}
|
|
278
|
+
for (i, (stem, _, _)) in bins.iter().enumerate() {
|
|
279
|
+
let entry = outputs[i].0;
|
|
280
|
+
let expect = entry.file_stem().and_then(|s| s.to_str()).unwrap_or("");
|
|
281
|
+
if expect != stem {
|
|
282
|
+
return Err(format!(
|
|
283
|
+
"build_many_via_cargo: stem mismatch at {}: {} vs {}",
|
|
284
|
+
i, stem, expect
|
|
285
|
+
));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
let batch_id = format!("many_{}", std::process::id());
|
|
290
|
+
let build_dir = tishlang_build_utils::create_build_dir("tish_build_many", &batch_id)?;
|
|
291
|
+
|
|
292
|
+
let runtime_path = tishlang_build_utils::find_runtime_path_for_project(project_root)?;
|
|
293
|
+
|
|
294
|
+
let runtime_features = runtime_features_for_cargo(features);
|
|
295
|
+
let runtime_refs: Vec<&str> = runtime_features.iter().map(String::as_str).collect();
|
|
296
|
+
let features_str = if runtime_refs.is_empty() {
|
|
297
|
+
String::new()
|
|
298
|
+
} else {
|
|
299
|
+
format!(", features = {:?}", runtime_refs)
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
let tokio_dep = if needs_tokio {
|
|
303
|
+
"\ntokio = { version = \"1\", features = [\"rt-multi-thread\", \"macros\"] }\n"
|
|
304
|
+
} else {
|
|
305
|
+
""
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
let native_deps: String = native_modules
|
|
309
|
+
.iter()
|
|
310
|
+
.filter(|m| m.use_path_dependency)
|
|
311
|
+
.map(|m| {
|
|
312
|
+
let path = m.crate_path.display().to_string().replace('\\', "/");
|
|
313
|
+
format!("{} = {{ path = {:?} }}\n", m.package_name, path)
|
|
314
|
+
})
|
|
315
|
+
.collect();
|
|
316
|
+
|
|
317
|
+
let mut more_deps = String::new();
|
|
318
|
+
more_deps.push_str(tokio_dep);
|
|
319
|
+
if !native_deps.is_empty() {
|
|
320
|
+
more_deps.push_str(&format!("\n{}", native_deps));
|
|
321
|
+
}
|
|
322
|
+
if !extra_dependencies_toml.trim().is_empty() {
|
|
323
|
+
more_deps.push_str(&format!("\n{}", extra_dependencies_toml));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
let tish_ui_path = std::path::Path::new(&runtime_path)
|
|
327
|
+
.parent()
|
|
328
|
+
.ok_or_else(|| "invalid tishlang_runtime path (no parent)".to_string())?
|
|
329
|
+
.join("tish_ui");
|
|
330
|
+
let ui_dep = if needs_ui {
|
|
331
|
+
format!(
|
|
332
|
+
"\ntishlang_ui = {{ path = {:?}, default-features = false, features = [\"runtime\"] }}\n",
|
|
333
|
+
tish_ui_path.display().to_string().replace('\\', "/")
|
|
334
|
+
)
|
|
335
|
+
} else {
|
|
336
|
+
String::new()
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
let mut bin_tables = String::new();
|
|
340
|
+
for (stem, rust_code, generated_native_rs) in &bins {
|
|
341
|
+
let bin_dir = build_dir.join("src/bin").join(stem);
|
|
342
|
+
fs::create_dir_all(&bin_dir).map_err(|e| format!("create bin dir: {}", e))?;
|
|
343
|
+
|
|
344
|
+
let rust_main = if generated_native_rs.is_some() {
|
|
345
|
+
inject_generated_native_mod(rust_code)
|
|
346
|
+
} else {
|
|
347
|
+
rust_code.clone()
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
fs::write(bin_dir.join("main.rs"), rust_main)
|
|
351
|
+
.map_err(|e| format!("write main.rs for {}: {}", stem, e))?;
|
|
352
|
+
if let Some(gen) = generated_native_rs {
|
|
353
|
+
fs::write(bin_dir.join("generated_native.rs"), gen)
|
|
354
|
+
.map_err(|e| format!("write generated_native.rs for {}: {}", stem, e))?;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
bin_tables.push_str(&format!(
|
|
358
|
+
r#"[[bin]]
|
|
359
|
+
name = "{stem}"
|
|
360
|
+
path = "src/bin/{stem}/main.rs"
|
|
361
|
+
|
|
362
|
+
"#
|
|
363
|
+
));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
let profile = nested_release_profile_toml();
|
|
367
|
+
let cargo_toml = format!(
|
|
368
|
+
r#"[package]
|
|
369
|
+
name = "tish_output_many"
|
|
370
|
+
version = "0.1.0"
|
|
371
|
+
edition = "2021"
|
|
372
|
+
|
|
373
|
+
{}{}
|
|
374
|
+
[dependencies]
|
|
375
|
+
tishlang_runtime = {{ path = {:?}{} }}
|
|
376
|
+
{}{}"#,
|
|
377
|
+
bin_tables, profile, runtime_path, features_str, more_deps, ui_dep
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
fs::write(build_dir.join("Cargo.toml"), cargo_toml)
|
|
381
|
+
.map_err(|e| format!("Cannot write Cargo.toml: {}", e))?;
|
|
142
382
|
|
|
143
383
|
let workspace_target = Path::new(&runtime_path)
|
|
144
384
|
.parent()
|
|
@@ -150,11 +390,15 @@ tishlang_runtime = {{ path = {:?}{} }}
|
|
|
150
390
|
.map(|t| t.join("release"))
|
|
151
391
|
.unwrap_or_else(|| build_dir.join("target").join("release"));
|
|
152
392
|
|
|
153
|
-
tishlang_build_utils::run_cargo_build(&build_dir, target_dir.as_deref())?;
|
|
393
|
+
tishlang_build_utils::run_cargo_build(&build_dir, target_dir.as_deref(), None)?;
|
|
154
394
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
395
|
+
for i in 0..bins.len() {
|
|
396
|
+
let stem = bins[i].0.as_str();
|
|
397
|
+
let output_path = outputs[i].1;
|
|
398
|
+
let binary = tishlang_build_utils::find_release_binary(binary_dir.as_path(), stem)?;
|
|
399
|
+
let target = tishlang_build_utils::resolve_output_path(output_path, stem);
|
|
400
|
+
tishlang_build_utils::copy_binary_to_output(&binary, &target)?;
|
|
401
|
+
}
|
|
158
402
|
|
|
159
403
|
Ok(())
|
|
160
404
|
}
|
|
@@ -176,6 +420,8 @@ mod tests {
|
|
|
176
420
|
#[test]
|
|
177
421
|
fn runtime_features_merges_full_and_specific() {
|
|
178
422
|
let f = runtime_features_for_cargo(&["full".to_string(), "http".to_string()]);
|
|
179
|
-
|
|
423
|
+
// `full` expands to every RUNTIME_CARGO_FEATURES entry; redundant `http` must not duplicate.
|
|
424
|
+
assert_eq!(f.len(), super::RUNTIME_CARGO_FEATURES.len());
|
|
425
|
+
assert_eq!(f.iter().filter(|x| *x == "http").count(), 1);
|
|
180
426
|
}
|
|
181
427
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//! Native build configuration (desktop binary vs iOS staticlib, cross-target).
|
|
2
|
+
|
|
3
|
+
use tishlang_compile::NativeEmitMode;
|
|
4
|
+
|
|
5
|
+
/// Output artifact kind for `tish build --target native`.
|
|
6
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
7
|
+
pub enum NativeArtifact {
|
|
8
|
+
#[default]
|
|
9
|
+
Bin,
|
|
10
|
+
StaticLib,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/// Options passed from the CLI into nested `cargo build`.
|
|
14
|
+
#[derive(Debug, Clone, Default)]
|
|
15
|
+
pub struct NativeBuildConfig {
|
|
16
|
+
pub artifact: NativeArtifact,
|
|
17
|
+
/// When set, run `cargo build --target <triple>` and skip `-C target-cpu=native`.
|
|
18
|
+
pub cargo_target: Option<String>,
|
|
19
|
+
pub emit_mode: NativeEmitMode,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
impl NativeBuildConfig {
|
|
23
|
+
pub fn desktop() -> Self {
|
|
24
|
+
Self::default()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pub fn ios_staticlib(triple: &str) -> Self {
|
|
28
|
+
Self {
|
|
29
|
+
artifact: NativeArtifact::StaticLib,
|
|
30
|
+
cargo_target: Some(triple.to_string()),
|
|
31
|
+
emit_mode: NativeEmitMode::EmbeddedLib,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pub fn is_cross_compile(&self) -> bool {
|
|
36
|
+
self.cargo_target.is_some()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Filter runtime features for iOS sandbox builds.
|
|
41
|
+
pub fn ios_runtime_features(features: &[String]) -> Vec<String> {
|
|
42
|
+
const ALLOW: &[&str] = &["http", "http-hyper", "regex", "timers"];
|
|
43
|
+
features
|
|
44
|
+
.iter()
|
|
45
|
+
.filter(|f| ALLOW.contains(&f.as_str()))
|
|
46
|
+
.cloned()
|
|
47
|
+
.collect()
|
|
48
|
+
}
|
|
@@ -10,10 +10,29 @@
|
|
|
10
10
|
//! emit Rust using `Vec<f64>` / fixed primitives instead of `Value` on hot paths.
|
|
11
11
|
|
|
12
12
|
mod build;
|
|
13
|
+
mod config;
|
|
14
|
+
|
|
15
|
+
pub use config::{ios_runtime_features, NativeArtifact, NativeBuildConfig};
|
|
13
16
|
|
|
14
17
|
use std::path::Path;
|
|
15
18
|
use tishlang_ast::Program;
|
|
16
19
|
|
|
20
|
+
/// Features for the embeddable VM (`cranelift` / `llvm` backends): merge CLI capability flags
|
|
21
|
+
/// (same set as `tish run` / `tish build --target native`) with features inferred from
|
|
22
|
+
/// `import … from 'tish:…'`. Programs that use globals only (`Promise`, `fetch`, `setTimeout`)
|
|
23
|
+
/// still need the former so [`tishlang_cranelift_runtime`] links `tishlang_vm` with matching gates.
|
|
24
|
+
fn merged_embed_runtime_features(cli: &[String], program: &Program) -> Vec<String> {
|
|
25
|
+
let mut out = cli.to_vec();
|
|
26
|
+
for f in tishlang_compile::extract_native_import_features(program) {
|
|
27
|
+
if !out.contains(&f) {
|
|
28
|
+
out.push(f);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
out.sort();
|
|
32
|
+
out.dedup();
|
|
33
|
+
out
|
|
34
|
+
}
|
|
35
|
+
|
|
17
36
|
/// Error from native compilation.
|
|
18
37
|
#[derive(Debug)]
|
|
19
38
|
pub struct NativeError {
|
|
@@ -40,6 +59,27 @@ pub fn compile_to_native(
|
|
|
40
59
|
features: &[String],
|
|
41
60
|
native_backend: &str,
|
|
42
61
|
optimize: bool,
|
|
62
|
+
) -> Result<(), NativeError> {
|
|
63
|
+
compile_to_native_with_config(
|
|
64
|
+
entry_path,
|
|
65
|
+
project_root,
|
|
66
|
+
output_path,
|
|
67
|
+
features,
|
|
68
|
+
native_backend,
|
|
69
|
+
optimize,
|
|
70
|
+
&NativeBuildConfig::desktop(),
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Compile a Tish project to a native binary or static library.
|
|
75
|
+
pub fn compile_to_native_with_config(
|
|
76
|
+
entry_path: &Path,
|
|
77
|
+
project_root: Option<&Path>,
|
|
78
|
+
output_path: &Path,
|
|
79
|
+
features: &[String],
|
|
80
|
+
native_backend: &str,
|
|
81
|
+
optimize: bool,
|
|
82
|
+
build_config: &NativeBuildConfig,
|
|
43
83
|
) -> Result<(), NativeError> {
|
|
44
84
|
let backend = match native_backend {
|
|
45
85
|
"rust" => Backend::Rust,
|
|
@@ -57,25 +97,44 @@ pub fn compile_to_native(
|
|
|
57
97
|
|
|
58
98
|
match backend {
|
|
59
99
|
Backend::Rust => {
|
|
100
|
+
let ios_cap = build_config.cargo_target.as_ref().map(|_| {
|
|
101
|
+
ios_runtime_features(features)
|
|
102
|
+
.into_iter()
|
|
103
|
+
.collect::<std::collections::HashSet<_>>()
|
|
104
|
+
});
|
|
105
|
+
let compile_features = if ios_cap.is_some() {
|
|
106
|
+
ios_runtime_features(features)
|
|
107
|
+
} else {
|
|
108
|
+
features.to_vec()
|
|
109
|
+
};
|
|
60
110
|
let (rust_code, native_modules, effective_features, native_build) =
|
|
61
|
-
tishlang_compile::
|
|
111
|
+
tishlang_compile::compile_project_full_emit(
|
|
62
112
|
entry_path,
|
|
63
113
|
project_root,
|
|
64
|
-
|
|
114
|
+
&compile_features,
|
|
65
115
|
optimize,
|
|
116
|
+
build_config.emit_mode,
|
|
117
|
+
ios_cap.as_ref(),
|
|
66
118
|
)
|
|
67
119
|
.map_err(|e| NativeError {
|
|
68
120
|
message: e.to_string(),
|
|
69
121
|
})?;
|
|
70
122
|
|
|
71
|
-
|
|
123
|
+
let features_for_cargo = if build_config.cargo_target.is_some() {
|
|
124
|
+
ios_runtime_features(&effective_features)
|
|
125
|
+
} else {
|
|
126
|
+
effective_features
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
crate::build::build_via_cargo_with_config(
|
|
72
130
|
&rust_code,
|
|
73
131
|
native_modules,
|
|
74
132
|
output_path,
|
|
75
|
-
&
|
|
133
|
+
&features_for_cargo,
|
|
76
134
|
&native_build.rust_dependencies_toml,
|
|
77
135
|
native_build.generated_native_rs.as_deref(),
|
|
78
136
|
project_root,
|
|
137
|
+
build_config,
|
|
79
138
|
)
|
|
80
139
|
.map_err(|e| NativeError { message: e })
|
|
81
140
|
}
|
|
@@ -93,8 +152,8 @@ pub fn compile_to_native(
|
|
|
93
152
|
let prog = tishlang_compile::merge_modules(modules)
|
|
94
153
|
.map(|m| m.program)
|
|
95
154
|
.map_err(|e| NativeError {
|
|
96
|
-
|
|
97
|
-
|
|
155
|
+
message: e.to_string(),
|
|
156
|
+
})?;
|
|
98
157
|
if optimize {
|
|
99
158
|
tishlang_opt::optimize(&prog)
|
|
100
159
|
} else {
|
|
@@ -118,7 +177,7 @@ pub fn compile_to_native(
|
|
|
118
177
|
})?
|
|
119
178
|
};
|
|
120
179
|
|
|
121
|
-
let cranelift_features =
|
|
180
|
+
let cranelift_features = merged_embed_runtime_features(features, &program);
|
|
122
181
|
tishlang_cranelift::compile_chunk_to_native(&chunk, output_path, &cranelift_features)
|
|
123
182
|
.map_err(|e| NativeError {
|
|
124
183
|
message: e.to_string(),
|
|
@@ -138,8 +197,8 @@ pub fn compile_to_native(
|
|
|
138
197
|
let prog = tishlang_compile::merge_modules(modules)
|
|
139
198
|
.map(|m| m.program)
|
|
140
199
|
.map_err(|e| NativeError {
|
|
141
|
-
|
|
142
|
-
|
|
200
|
+
message: e.to_string(),
|
|
201
|
+
})?;
|
|
143
202
|
if optimize {
|
|
144
203
|
tishlang_opt::optimize(&prog)
|
|
145
204
|
} else {
|
|
@@ -160,7 +219,7 @@ pub fn compile_to_native(
|
|
|
160
219
|
message: e.to_string(),
|
|
161
220
|
})?
|
|
162
221
|
};
|
|
163
|
-
let llvm_features =
|
|
222
|
+
let llvm_features = merged_embed_runtime_features(features, &program);
|
|
164
223
|
tishlang_llvm::compile_chunk_to_native(&chunk, output_path, &llvm_features)
|
|
165
224
|
.map_err(|e| NativeError { message: e.message })
|
|
166
225
|
}
|
|
@@ -249,7 +308,7 @@ pub fn compile_program_to_native(
|
|
|
249
308
|
message: e.to_string(),
|
|
250
309
|
})?
|
|
251
310
|
};
|
|
252
|
-
let cranelift_features =
|
|
311
|
+
let cranelift_features = merged_embed_runtime_features(features, &program);
|
|
253
312
|
tishlang_cranelift::compile_chunk_to_native(&chunk, output_path, &cranelift_features)
|
|
254
313
|
.map_err(|e| NativeError {
|
|
255
314
|
message: e.to_string(),
|
|
@@ -275,13 +334,81 @@ pub fn compile_program_to_native(
|
|
|
275
334
|
message: e.to_string(),
|
|
276
335
|
})?
|
|
277
336
|
};
|
|
278
|
-
let llvm_features =
|
|
337
|
+
let llvm_features = merged_embed_runtime_features(features, &program);
|
|
279
338
|
tishlang_llvm::compile_chunk_to_native(&chunk, output_path, &llvm_features)
|
|
280
339
|
.map_err(|e| NativeError { message: e.message })
|
|
281
340
|
}
|
|
282
341
|
}
|
|
283
342
|
}
|
|
284
343
|
|
|
344
|
+
/// Compile multiple entry `.tish` files to native binaries in **one** nested Cargo build.
|
|
345
|
+
///
|
|
346
|
+
/// Intended for integration tests and batch tooling; keeps production [`compile_to_native`] behavior
|
|
347
|
+
/// unchanged when `TISH_FAST_NATIVE_BUILD` is unset.
|
|
348
|
+
pub fn compile_many_to_native(
|
|
349
|
+
entries: &[(&Path, &Path)],
|
|
350
|
+
project_root: Option<&Path>,
|
|
351
|
+
features: &[String],
|
|
352
|
+
optimize: bool,
|
|
353
|
+
) -> Result<(), NativeError> {
|
|
354
|
+
let mut bins: Vec<(String, String, Option<String>)> = Vec::with_capacity(entries.len());
|
|
355
|
+
let mut merged_native_modules: Vec<tishlang_compile::ResolvedNativeModule> = Vec::new();
|
|
356
|
+
let mut merged_features: Vec<String> = features.to_vec();
|
|
357
|
+
let mut merged_extra_deps = String::new();
|
|
358
|
+
let mut needs_tokio = false;
|
|
359
|
+
let mut needs_ui = false;
|
|
360
|
+
|
|
361
|
+
for (entry_path, _) in entries {
|
|
362
|
+
let (rust_code, native_modules, effective_features, native_build) =
|
|
363
|
+
tishlang_compile::compile_project_full(entry_path, project_root, features, optimize)
|
|
364
|
+
.map_err(|e| NativeError {
|
|
365
|
+
message: e.to_string(),
|
|
366
|
+
})?;
|
|
367
|
+
let stem = entry_path
|
|
368
|
+
.file_stem()
|
|
369
|
+
.and_then(|s| s.to_str())
|
|
370
|
+
.ok_or_else(|| NativeError {
|
|
371
|
+
message: format!("invalid entry path: {}", entry_path.display()),
|
|
372
|
+
})?
|
|
373
|
+
.to_string();
|
|
374
|
+
|
|
375
|
+
for f in &effective_features {
|
|
376
|
+
if !merged_features.contains(f) {
|
|
377
|
+
merged_features.push(f.clone());
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
for m in native_modules {
|
|
381
|
+
let dup = merged_native_modules
|
|
382
|
+
.iter()
|
|
383
|
+
.any(|x| x.package_name == m.package_name && x.crate_path == m.crate_path);
|
|
384
|
+
if !dup {
|
|
385
|
+
merged_native_modules.push(m);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
let extra = native_build.rust_dependencies_toml.trim();
|
|
389
|
+
if !extra.is_empty() {
|
|
390
|
+
merged_extra_deps.push_str(extra);
|
|
391
|
+
merged_extra_deps.push('\n');
|
|
392
|
+
}
|
|
393
|
+
needs_tokio |= crate::build::rust_code_needs_tokio(&rust_code);
|
|
394
|
+
needs_ui |= rust_code.contains("tishlang_ui");
|
|
395
|
+
bins.push((stem, rust_code, native_build.generated_native_rs));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
let merged_extra = merged_extra_deps.trim();
|
|
399
|
+
crate::build::build_many_via_cargo(
|
|
400
|
+
bins,
|
|
401
|
+
merged_native_modules,
|
|
402
|
+
&merged_features,
|
|
403
|
+
merged_extra,
|
|
404
|
+
needs_tokio,
|
|
405
|
+
needs_ui,
|
|
406
|
+
entries,
|
|
407
|
+
project_root,
|
|
408
|
+
)
|
|
409
|
+
.map_err(|e| NativeError { message: e })
|
|
410
|
+
}
|
|
411
|
+
|
|
285
412
|
enum Backend {
|
|
286
413
|
Rust,
|
|
287
414
|
Cranelift,
|
|
@@ -320,10 +320,13 @@ mod tests {
|
|
|
320
320
|
Statement::Block { statements, .. } => statements,
|
|
321
321
|
_ => panic!("expected block body"),
|
|
322
322
|
};
|
|
323
|
-
assert_eq!(
|
|
323
|
+
assert_eq!(
|
|
324
|
+
stmts.len(),
|
|
325
|
+
3,
|
|
326
|
+
"expected expr; const; if as siblings — got {stmts:?}"
|
|
327
|
+
);
|
|
324
328
|
assert!(matches!(stmts[0], Statement::ExprStmt { .. }));
|
|
325
329
|
assert!(matches!(stmts[1], Statement::VarDecl { .. }));
|
|
326
330
|
assert!(matches!(stmts[2], Statement::If { .. }));
|
|
327
331
|
}
|
|
328
|
-
|
|
329
332
|
}
|