From 677bd82d751df1a252f4a4b28446bc84ed17b722 Mon Sep 17 00:00:00 2001 From: Tim Fischer Date: Fri, 31 Jul 2026 16:07:58 +0200 Subject: [PATCH 1/3] script: Upgrade templating engine from Tera 1 to Tera 2 Tera 2.0 breaks both the Rust API and the template syntax, so the dependabot bump in #329 does not compile. This fixes both sides. Rust API: - `Context::into_json()` is gone, and Tera 2's `Context` is write-only (no `Serialize`, no iterator, `data` is `pub(crate)`), so there is no way to recover the context for `script template-json`. Accumulate the values in a `serde_json::Map` instead and convert with `Context::from_serialize` right before rendering. - `render_str` gained an explicit `autoescape` argument, because a one-off template has no filename to infer escaping from. Templates: all eleven built-in formats failed to render. Array and tuple fields need `foo[0]` rather than `foo.0`, tests take keyword arguments (`is starting_with(pat=root)`), and `trim_start_matches` became `trim_start(pat=...)`. Beyond the straight port: - Register the template under its own name and use `render` instead of rendering it as a one-off, so errors point at `my_format.tera:12:54` rather than `__tera_one_off:12:54`. This also means unknown filters and tests are reported before rendering starts. Autoescaping is disabled explicitly rather than relying on the filename suffix, so a user template that happens to end in `.html` cannot silently start escaping its own output. - Enable the `preserve_order` feature. Tera 2 ships `default = []` and its `Map` is a `std::collections::HashMap`, so map iteration order varied between runs; Tera 1 used a `BTreeMap` and was deterministic. No built-in template iterates a map, but a custom template iterating `global_defines` would produce non-reproducible scripts. The feature switches Tera to `indexmap`, already a direct dependency, so it adds no transitive dependencies. Verified byte-identical output against Tera 1 across all twelve formats with the default, `--compilation-mode separate --source-annotations` and `-D` flag combinations, plus `--only-defines`, `--only-includes` and `--relative-path`. Drops 44 transitive dependencies, adds none. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 6 + Cargo.lock | 532 +++--------------------------- Cargo.toml | 2 +- src/cmd/script.rs | 141 ++++++-- src/script_fmt/flist-plus.tera | 10 +- src/script_fmt/flist.tera | 4 +- src/script_fmt/formality_tcl.tera | 4 +- src/script_fmt/genus_tcl.tera | 4 +- src/script_fmt/precision_tcl.tera | 2 +- src/script_fmt/riviera_tcl.tera | 4 +- src/script_fmt/synopsys_tcl.tera | 4 +- src/script_fmt/vcs_sh.tera | 4 +- src/script_fmt/verilator_sh.tera | 2 +- src/script_fmt/vivado_tcl.tera | 2 +- src/script_fmt/vsim_tcl.tera | 4 +- 15 files changed, 196 insertions(+), 529 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88b2562c..1554fe24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## Unreleased +### Breaking Changes +- script: Upgrade the templating engine from Tera 1 to Tera 2. Custom templates passed to `bender script template --template` may need updating. See the [Tera migration guide](https://github.com/Keats/tera/blob/master/MIGRATION.md) for the full list. The output of all built-in formats is unchanged. + ### Changed - `bender clone`: primary flag for the checkout directory is now `--working-dir`, matching `bender snapshot`'s flag for the same concept; `-p`/`--path` are kept as hidden aliases for backwards compatibility. +### Changed +- script: Template errors now report the template name and source location (e.g. `my_format.tera:12:54`), and unknown filters or tests are reported before rendering starts. + ## 0.32.1 - 2026-07-07 ### Added - Add `git_submodules` config field and `--git-submodules ` flag (env `BENDER_GIT_SUBMODULES`) to control cloning of dependency submodules; defaults to `true`, the flag overrides the configured value in either direction (https://github.com/pulp-platform/bender/pull/314). diff --git a/Cargo.lock b/Cargo.lock index 43c761db..d7265cb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,22 +4,13 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anstream" version = "1.0.0" @@ -93,15 +84,9 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - [[package]] name = "bender" version = "0.32.1" @@ -153,9 +138,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "blake2" @@ -177,9 +162,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.3" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cee35f73844aa3014bb606320a6c1f010249dbdf43342fe54b5a4f6a8ed4b79" +checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530" dependencies = [ "memchr", "regex-automata", @@ -194,15 +179,15 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytes" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cc" -version = "1.2.66" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "shlex", @@ -214,44 +199,11 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "chrono" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" -dependencies = [ - "iana-time-zone", - "num-traits", - "windows-link", -] - -[[package]] -name = "chrono-tz" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" -dependencies = [ - "chrono", - "chrono-tz-build", - "phf", -] - -[[package]] -name = "chrono-tz-build" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" -dependencies = [ - "parse-zoneinfo", - "phf", - "phf_codegen", -] - [[package]] name = "clap" -version = "4.6.4" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" +checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf" dependencies = [ "clap_builder", "clap_derive", @@ -259,9 +211,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.2" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" +checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078" dependencies = [ "anstream", "anstyle", @@ -271,9 +223,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.7" +version = "4.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8b397918185f0161ff3d6fcaa9e4bfc09b8367caf6e1d4a2848e5477ed027b" +checksum = "b1f84a88507dbd05c695f2cb5e8558e747179134005e9893882dec964190ed89" dependencies = [ "clap", ] @@ -334,37 +286,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "crossbeam-deque" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" - [[package]] name = "crypto-common" version = "0.1.7" @@ -437,12 +358,6 @@ dependencies = [ "syn 3.0.3", ] -[[package]] -name = "deunicode" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" - [[package]] name = "diff" version = "0.1.13" @@ -495,9 +410,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "encode_unicode" @@ -545,9 +460,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "find-msvc-tools" @@ -617,7 +532,7 @@ checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -687,30 +602,6 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" -[[package]] -name = "globset" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "globwalk" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" -dependencies = [ - "bitflags", - "ignore", - "walkdir", -] - [[package]] name = "hashbrown" version = "0.17.1" @@ -729,55 +620,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" -[[package]] -name = "humansize" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" -dependencies = [ - "libm", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ignore" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe112b004901c62c2faa11f4f75e9864e0cc5af8da71c9115d184a3aa888749f" -dependencies = [ - "crossbeam-deque", - "globset", - "log", - "memchr", - "regex-automata", - "same-file", - "walkdir", - "winapi-util", -] - [[package]] name = "indexmap" version = "2.14.0" @@ -852,29 +694,17 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" - -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libredox" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" dependencies = [ "libc", ] @@ -911,9 +741,9 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "memchr" -version = "2.8.2" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "miette" @@ -934,29 +764,20 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "mio" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", "windows-sys", ] -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - [[package]] name = "once_cell" version = "1.21.4" @@ -1008,107 +829,12 @@ dependencies = [ "windows-link", ] -[[package]] -name = "parse-zoneinfo" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" -dependencies = [ - "regex", -] - [[package]] name = "pathdiff" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pest" -version = "2.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47627dd7305c6a2d6c8c6bcd24c5a4c17dbbf425f4f9c5313e724b38fc9782e9" -dependencies = [ - "memchr", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b4254325ecad416ab689e27ba51da03ba01a9632bc6e108f5fe7c3c4ad29d58" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c4c0e91ead7a8f7acecbca6f003fc2e8282b1dbe2dd9c9d2f16aba42995e0a7" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "pest_meta" -version = "2.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9744bc48116fee06334924bb5f2bad41eed5e89bd26e29b0b799f9a3f82c210" -dependencies = [ - "pest", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared", - "rand", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - [[package]] name = "pin-project-lite" version = "0.2.17" @@ -1117,18 +843,9 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "portable-atomic" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" [[package]] name = "predicates" @@ -1169,18 +886,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -1191,36 +908,6 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - [[package]] name = "redox_syscall" version = "0.5.18" @@ -1255,9 +942,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -1398,28 +1085,12 @@ dependencies = [ "libc", ] -[[package]] -name = "siphasher" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" - [[package]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" -[[package]] -name = "slug" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" -dependencies = [ - "deunicode", - "wasm-bindgen", -] - [[package]] name = "smallvec" version = "1.15.2" @@ -1428,9 +1099,9 @@ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys", @@ -1479,9 +1150,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.118" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ "proc-macro2", "quote", @@ -1523,24 +1194,12 @@ dependencies = [ [[package]] name = "tera" -version = "1.20.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8004bca281f2d32df3bacd59bc67b312cb4c70cea46cbd79dbe8ac5ed206722" -dependencies = [ - "chrono", - "chrono-tz", - "globwalk", - "humansize", - "lazy_static", - "percent-encoding", - "pest", - "pest_derive", - "rand", - "regex", +checksum = "511f07fd91a70e92efbe4793d111aaa9035f8474dd157aaa1e31e7c27f5051da" +dependencies = [ + "indexmap", "serde", - "serde_json", - "slug", - "unicode-segmentation", ] [[package]] @@ -1597,13 +1256,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 3.0.3", ] [[package]] @@ -1618,24 +1277,12 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-segmentation" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" - [[package]] name = "unicode-width" version = "0.1.14" @@ -1729,7 +1376,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", "wasm-bindgen-shared", ] @@ -1761,65 +1408,12 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -1835,28 +1429,8 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" -[[package]] -name = "zerocopy" -version = "0.8.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", -] - [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index 3807ae94..89116775 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ tempfile = "3.5" glob = "0.3" walkdir = "2" subst = "0.3" -tera = "1.19" +tera = { version = "2.0", features = ["preserve_order"] } miette = "7.6.0" thiserror = "2.0.18" owo-colors = { version = "4.2.3", features = ["supports-colors"] } diff --git a/src/cmd/script.rs b/src/cmd/script.rs index c88b0ab4..34503591 100644 --- a/src/cmd/script.rs +++ b/src/cmd/script.rs @@ -3,6 +3,7 @@ //! The `script` subcommand. +use std::borrow::Cow; use std::io::Write; use std::path::{Path, PathBuf}; @@ -16,7 +17,7 @@ use clap::{ArgAction, Args, Subcommand, ValueEnum}; use indexmap::{IndexMap, IndexSet}; use miette::{Context as _, IntoDiagnostic as _}; use serde::Serialize; -use tera::{Context, Tera}; +use tera::Tera; use tokio::runtime::Runtime; use crate::Result; @@ -414,18 +415,22 @@ pub fn run(sess: &Session, args: &ScriptArgs) -> Result<()> { #[cfg(not(feature = "slang"))] let unparseable_paths = std::collections::HashSet::::new(); - let mut tera_context = Context::new(); + let mut tera_context = TemplateContext::new(); let mut only_args = OnlyArgs { defines: false, includes: false, sources: false, }; - // Generate the corresponding output. - let template_content = match &args.format { + // Generate the corresponding output. The name is what template errors are reported against, + // so custom templates are named after the file the user passed. + let (template_name, template_content): (&str, Cow<'_, str>) = match &args.format { ScriptFormat::Flist { relative_path } => { tera_context.insert("relativize_path", relative_path); - include_str!("../script_fmt/flist.tera") + ( + "flist.tera", + include_str!("../script_fmt/flist.tera").into(), + ) } ScriptFormat::FlistPlus { relative_path, @@ -433,7 +438,10 @@ pub fn run(sess: &Session, args: &ScriptArgs) -> Result<()> { } => { tera_context.insert("relativize_path", relative_path); only_args = only.clone(); - include_str!("../script_fmt/flist-plus.tera") + ( + "flist-plus.tera", + include_str!("../script_fmt/flist-plus.tera").into(), + ) } ScriptFormat::Vsim { vlog_args, @@ -441,7 +449,10 @@ pub fn run(sess: &Session, args: &ScriptArgs) -> Result<()> { } => { tera_context.insert("vlog_args", vlog_args); tera_context.insert("vcom_args", vcom_args); - include_str!("../script_fmt/vsim_tcl.tera") + ( + "vsim_tcl.tera", + include_str!("../script_fmt/vsim_tcl.tera").into(), + ) } ScriptFormat::Vcs { vlogan_bin, @@ -453,11 +464,17 @@ pub fn run(sess: &Session, args: &ScriptArgs) -> Result<()> { tera_context.insert("vhdlan_args", vhdlan_args); tera_context.insert("vlogan_bin", vlogan_bin); tera_context.insert("vhdlan_bin", vhdlan_bin); - include_str!("../script_fmt/vcs_sh.tera") + ( + "vcs_sh.tera", + include_str!("../script_fmt/vcs_sh.tera").into(), + ) } ScriptFormat::Verilator { vlt_args } => { tera_context.insert("vlt_args", vlt_args); - include_str!("../script_fmt/verilator_sh.tera") + ( + "verilator_sh.tera", + include_str!("../script_fmt/verilator_sh.tera").into(), + ) } ScriptFormat::Synopsys { verilog_args, @@ -465,18 +482,30 @@ pub fn run(sess: &Session, args: &ScriptArgs) -> Result<()> { } => { tera_context.insert("verilog_args", verilog_args); tera_context.insert("vhdl_args", vhdl_args); - include_str!("../script_fmt/synopsys_tcl.tera") + ( + "synopsys_tcl.tera", + include_str!("../script_fmt/synopsys_tcl.tera").into(), + ) } - ScriptFormat::Formality => include_str!("../script_fmt/formality_tcl.tera"), + ScriptFormat::Formality => ( + "formality_tcl.tera", + include_str!("../script_fmt/formality_tcl.tera").into(), + ), ScriptFormat::Riviera { vlog_args, vcom_args, } => { tera_context.insert("vlog_args", vlog_args); tera_context.insert("vcom_args", vcom_args); - include_str!("../script_fmt/riviera_tcl.tera") + ( + "riviera_tcl.tera", + include_str!("../script_fmt/riviera_tcl.tera").into(), + ) } - ScriptFormat::Genus => include_str!("../script_fmt/genus_tcl.tera"), + ScriptFormat::Genus => ( + "genus_tcl.tera", + include_str!("../script_fmt/genus_tcl.tera").into(), + ), ScriptFormat::Vivado { no_simset, only } | ScriptFormat::VivadoSim { no_simset, only } => { only_args = only.clone(); tera_context.insert("vivado_filesets", &{ @@ -486,19 +515,32 @@ pub fn run(sess: &Session, args: &ScriptArgs) -> Result<()> { vec!["", " -simset"] } }); - include_str!("../script_fmt/vivado_tcl.tera") - } - ScriptFormat::Precision => include_str!("../script_fmt/precision_tcl.tera"), - ScriptFormat::Template { template } => { - &std::fs::read_to_string(template).into_diagnostic()? + ( + "vivado_tcl.tera", + include_str!("../script_fmt/vivado_tcl.tera").into(), + ) } - ScriptFormat::TemplateJson => JSON, + ScriptFormat::Precision => ( + "precision_tcl.tera", + include_str!("../script_fmt/precision_tcl.tera").into(), + ), + ScriptFormat::Template { template } => ( + template, + std::fs::read_to_string(template) + .into_diagnostic() + .wrap_err_with(|| format!("Failed to read template {template}."))? + .into(), + ), + ScriptFormat::TemplateJson => (JSON, JSON.into()), }; emit_template( sess, tera_context, - template_content, + ScriptTemplate { + name: template_name, + content: &template_content, + }, args, only_args, srcs, @@ -775,10 +817,38 @@ fn add_defines(defines: &mut IndexMap>, define_args: &[St static JSON: &str = "json"; +/// The values handed to the script templates. +/// +/// Kept as a JSON map rather than a `tera::Context` so that `--format template-json` can dump it +/// verbatim; it is turned into a context right before rendering. +#[derive(Default)] +struct TemplateContext(serde_json::Map); + +impl TemplateContext { + fn new() -> Self { + Self::default() + } + + /// Serialize `val` and store it under `key`. Panics if `val` is not serializable, like + /// `tera::Context::insert` does. + fn insert(&mut self, key: &str, val: &T) { + self.0.insert( + key.to_string(), + serde_json::to_value(val).expect("template value must be serializable"), + ); + } +} + +/// The template to render: its body, plus the name that template errors are reported against. +struct ScriptTemplate<'a> { + name: &'a str, + content: &'a str, +} + fn emit_template( sess: &Session, - mut tera_context: Context, - template: &str, + mut tera_context: TemplateContext, + template: ScriptTemplate<'_>, args: &ScriptArgs, only: OnlyArgs, srcs: Vec, @@ -994,23 +1064,40 @@ fn emit_template( }; tera_context.insert("all_verilog", &all_verilog); tera_context.insert("all_vhdl", &all_vhdl); - if !unknown_files.is_empty() && template.contains("file_type") { + if !unknown_files.is_empty() && template.content.contains("file_type") { Warnings::UnknownFileType(unknown_files.iter().map(|x| x.file.clone()).collect()).emit(); } tera_context.insert("source_annotations", &args.source_annotations); tera_context.insert("compilation_mode", &args.compilation_mode); - if template == "json" { - let _ = writeln!(std::io::stdout(), "{:#}", tera_context.into_json()); + if template.content == JSON { + let _ = writeln!( + std::io::stdout(), + "{:#}", + serde_json::Value::Object(tera_context.0) + ); return Ok(()); } + let context = tera::Context::from_serialize(&tera_context.0) + .into_diagnostic() + .wrap_err("Failed to build template context.")?; + + // Register the template under its own name rather than rendering it as a one-off, so that + // parse and render errors point at `::` instead of `__tera_one_off`. + let mut tera = Tera::default(); + // Scripts are Tcl/shell/flist, never HTML, so autoescaping must stay off no matter what the + // user's template file happens to be called. + tera.autoescape_on(Vec::<&str>::new()); + tera.add_raw_template(template.name, template.content) + .into_diagnostic() + .wrap_err("Failed to parse template.")?; + let _ = write!( std::io::stdout(), "{}", - Tera::default() - .render_str(template, &tera_context) + tera.render(template.name, &context) .into_diagnostic() .wrap_err("Failed to render template.")? ); diff --git a/src/script_fmt/flist-plus.tera b/src/script_fmt/flist-plus.tera index 1439979c..a4ee61f7 100644 --- a/src/script_fmt/flist-plus.tera +++ b/src/script_fmt/flist-plus.tera @@ -1,7 +1,7 @@ {% for incdir in all_incdirs -%} {%- if relativize_path -%} -+incdir+{% if incdir is starting_with(root) -%} -{{ incdir | replace(from=root, to='') | trim_start_matches(pat='/') }} ++incdir+{% if incdir is starting_with(pat=root) -%} +{{ incdir | replace(from=root, to='') | trim_start(pat='/') }} {% else -%} {{ incdir }} {% endif -%} @@ -10,7 +10,7 @@ {% endif -%} {%- endfor -%} {%- for define in all_defines -%} -+define+{{ define.0 }}{% if define.1 %}={{ define.1 }}{% endif %} ++define+{{ define[0] }}{% if define[1] %}={{ define[1] }}{% endif %} {% endfor -%} {%- for file_group in srcs -%} {%- if source_annotations -%} @@ -21,8 +21,8 @@ // {{ file.comment }} {% endif %}{% endif -%} {%- if relativize_path -%} -{%- if file.file is starting_with(root) -%} -{{ file.file | replace(from=root, to='') | trim_start_matches(pat='/') }} +{%- if file.file is starting_with(pat=root) -%} +{{ file.file | replace(from=root, to='') | trim_start(pat='/') }} {% else -%} {{ file.file }} {% endif -%} diff --git a/src/script_fmt/flist.tera b/src/script_fmt/flist.tera index 44215273..464538a3 100644 --- a/src/script_fmt/flist.tera +++ b/src/script_fmt/flist.tera @@ -7,8 +7,8 @@ // {{ file.comment }} {% endif %}{% endif -%} {%- if relativize_path -%} -{%- if file.file is starting_with(root) -%} -{{ file.file | replace(from=root, to='') | trim_start_matches(pat='/') }} +{%- if file.file is starting_with(pat=root) -%} +{{ file.file | replace(from=root, to='') | trim_start(pat='/') }} {% else -%} {{ file.file }} {% endif -%} diff --git a/src/script_fmt/formality_tcl.tera b/src/script_fmt/formality_tcl.tera index 08b6b8a9..da5fc6a7 100644 --- a/src/script_fmt/formality_tcl.tera +++ b/src/script_fmt/formality_tcl.tera @@ -9,7 +9,7 @@ set search_path $search_path_initial {% endfor %} {% if abort_on_error %}if {[catch { {% endif %}{% if group.file_type == 'verilog' %}read_sverilog{% elif group.file_type == 'vhdl' %}read_vhdl{% endif %} -r \ {% for define in group.defines %}{% if loop.first %}-define { \ - {% endif %}{{ define.0 }}{% if define.1 %}={{ define.1 }}{% endif %}{% if loop.last %} \ + {% endif %}{{ define[0] }}{% if define[1] %}={{ define[1] }}{% endif %}{% if loop.last %} \ } \ {% else %} \ {% endif %}{% endfor %}[list \ @@ -30,7 +30,7 @@ set search_path $search_path_initial {% endfor %} {% if abort_on_error %}if {[catch { {% endif %}read_sverilog -r \ {% for define in all_defines %}{% if loop.first %}-define { \ - {% endif %}{{ define.0 }}{% if define.1 %}={{ define.1 }}{% endif %}{% if loop.last %} \ + {% endif %}{{ define[0] }}{% if define[1] %}={{ define[1] }}{% endif %}{% if loop.last %} \ } \ {% else %} \ {% endif %}{% endfor %}[list \ diff --git a/src/script_fmt/genus_tcl.tera b/src/script_fmt/genus_tcl.tera index e25a455b..95a06cc1 100644 --- a/src/script_fmt/genus_tcl.tera +++ b/src/script_fmt/genus_tcl.tera @@ -15,7 +15,7 @@ set search_path $search_path_initial {% if group.file_type == 'verilog' %}read_hdl -language sv \ {% elif group.file_type == 'vhdl' %}read_hdl -language vhdl \ {% endif %}{% for define in group.defines %}{% if loop.first %}-define { \ - {% endif %}{{ define.0 }}{% if define.1 %}={{ define.1 }}{% endif %}{% if loop.last %} \ + {% endif %}{{ define[0] }}{% if define[1] %}={{ define[1] }}{% endif %}{% if loop.last %} \ } \ {% else %} \ {% endif %}{% endfor %}[list \ @@ -37,7 +37,7 @@ set_db init_hdl_search_path $search_path {% if abort_on_error %}if {[catch { {% endif %}read_hdl -language sv \ {% for define in all_defines %}{% if loop.first %}-define { \ - {% endif %}{{ define.0 }}{% if define.1 %}={{ define.1 }}{% endif %}{% if loop.last %} \ + {% endif %}{{ define[0] }}{% if define[1] %}={{ define[1] }}{% endif %}{% if loop.last %} \ } \ {% else %} \ {% endif %}{% endfor %}[list \ diff --git a/src/script_fmt/precision_tcl.tera b/src/script_fmt/precision_tcl.tera index 8b7c6dc7..fcf1b960 100644 --- a/src/script_fmt/precision_tcl.tera +++ b/src/script_fmt/precision_tcl.tera @@ -7,7 +7,7 @@ setup_design -search_path $ROOT {% for define in all_defines %}{% if loop.first %} # Set globally all defines for the (S)Verilog sources. setup_design -defines { \ - {% endif %}+define+{{ define.0 }}{% if define.1 %}={{ define.1 }}{% endif %}{% if loop.last %} + {% endif %}+define+{{ define[0] }}{% if define[1] %}={{ define[1] }}{% endif %}{% if loop.last %} {% else %} \ {% endif %}{% endfor %}{# Separate compilation: one block per source group -#} diff --git a/src/script_fmt/riviera_tcl.tera b/src/script_fmt/riviera_tcl.tera index 268f90fd..508f10e4 100644 --- a/src/script_fmt/riviera_tcl.tera +++ b/src/script_fmt/riviera_tcl.tera @@ -5,7 +5,7 @@ vlib work {% if compilation_mode == 'separate' %}{% for group in srcs %}{% if group.file_type == 'verilog' or group.file_type == 'vhdl' %}{% if source_annotations %}# {{ group.metadata }} {% endif %}{% if abort_on_error %}if {[catch { {% endif %}{% if group.file_type == 'verilog' %}vlog -sv \ {% for tmp_arg in vlog_args %}{{ tmp_arg }} \ - {% endfor %}{% for define in group.defines %}"+define+{{ define.0 }}{% if define.1 %}={{ define.1 }}{% endif %}" \ + {% endfor %}{% for define in group.defines %}"+define+{{ define[0] }}{% if define[1] %}={{ define[1] }}{% endif %}" \ {% endfor %}{% for incdir in group.incdirs %}"+incdir+{{ incdir | replace(from=root, to='$ROOT') }}" \ {% endfor %}{% elif group.file_type == 'vhdl' %}vcom -2008 \ {% for tmp_arg in vcom_args %}{{ tmp_arg }} \ @@ -24,7 +24,7 @@ vlib work {# Verilog sources -#} {%- for file in all_verilog %}{% if loop.first %}{% if abort_on_error %}if {[catch { {% endif %}vlog -sv \ {% for tmp_arg in vlog_args %}{{ tmp_arg }} \ - {% endfor %}{% for define in all_defines %}"+define+{{ define.0 }}{% if define.1 %}={{ define.1 }}{% endif %}" \ + {% endfor %}{% for define in all_defines %}"+define+{{ define[0] }}{% if define[1] %}={{ define[1] }}{% endif %}" \ {% endfor %}{% for incdir in all_incdirs %}"+incdir+{{ incdir | replace(from=root, to='$ROOT') }}" \ {% endfor %}{% endif %}{% if source_annotations %}{% if file.comment %}# {{ file.comment }} {% endif %}{% endif %}"{{ file.file | replace(from=root, to='$ROOT') }}" {% if not loop.last %}\ diff --git a/src/script_fmt/synopsys_tcl.tera b/src/script_fmt/synopsys_tcl.tera index a5847a7b..39c4c612 100644 --- a/src/script_fmt/synopsys_tcl.tera +++ b/src/script_fmt/synopsys_tcl.tera @@ -20,7 +20,7 @@ analyze -format {% if group.file_type == 'verilog' %}sv{% elif group.file_type = {%- endif -%} {%- for define in group.defines -%} {%- if loop.first %}-define { \ - {% endif %}{{ define.0 }}{% if define.1 %}={{ define.1 }}{% endif %}{% if loop.last %} \ + {% endif %}{{ define[0] }}{% if define[1] %}={{ define[1] }}{% endif %}{% if loop.last %} \ } \ {% else %} \ {% endif %}{% endfor %}[list \ @@ -48,7 +48,7 @@ analyze -format sv \ {% endfor -%} {%- for define in all_defines -%} {%- if loop.first %}-define { \ - {% endif %}{{ define.0 }}{% if define.1 %}={{ define.1 }}{% endif %}{% if loop.last %} \ + {% endif %}{{ define[0] }}{% if define[1] %}={{ define[1] }}{% endif %}{% if loop.last %} \ } \ {% else %} \ {% endif %}{% endfor %}[list \ diff --git a/src/script_fmt/vcs_sh.tera b/src/script_fmt/vcs_sh.tera index 16dc3b66..3a128868 100644 --- a/src/script_fmt/vcs_sh.tera +++ b/src/script_fmt/vcs_sh.tera @@ -10,7 +10,7 @@ ROOT="{{ root }}" {% if group.file_type == 'verilog' %}{{ vlogan_bin }} -sverilog \ -full64 \ {% for tmp_arg in vlogan_args %}{{ tmp_arg }} \ - {% endfor %}{% for define in group.defines %}"+define+{{ define.0 }}{% if define.1 %}={{ define.1 }}{% endif %}" \ + {% endfor %}{% for define in group.defines %}"+define+{{ define[0] }}{% if define[1] %}={{ define[1] }}{% endif %}" \ {% endfor %}{% for incdir in group.incdirs %}"+incdir+{{ incdir | replace(from=root, to='$ROOT') }}" \ {% endfor %}{% elif group.file_type == 'vhdl' %}{{ vhdlan_bin }} \ {% for tmp_arg in vhdlan_args %}{{ tmp_arg }} \ @@ -27,7 +27,7 @@ ROOT="{{ root }}" {%- for file in all_verilog %}{% if loop.first %}{{ vlogan_bin }} -sverilog \ -full64 \ {% for tmp_arg in vlogan_args %}{{ tmp_arg }} \ - {% endfor %}{% for define in all_defines %}"+define+{{ define.0 }}{% if define.1 %}={{ define.1 }}{% endif %}" \ + {% endfor %}{% for define in all_defines %}"+define+{{ define[0] }}{% if define[1] %}={{ define[1] }}{% endif %}" \ {% endfor %}{% for incdir in all_incdirs %}"+incdir+{{ incdir | replace(from=root, to='$ROOT') }}" \ {% endfor %}{% endif %}"{{ file.file | replace(from=root, to='$ROOT') }}" {% if not loop.last %}\ {% endif %}{% if loop.last %} diff --git a/src/script_fmt/verilator_sh.tera b/src/script_fmt/verilator_sh.tera index bd990e49..2b0fbd89 100644 --- a/src/script_fmt/verilator_sh.tera +++ b/src/script_fmt/verilator_sh.tera @@ -2,7 +2,7 @@ {%- if group.file_type == 'verilog' %} {% for tmp_arg in vlt_args %}{{ tmp_arg }} {% endfor -%} -{%- for define in group.defines %}+define+{{ define.0 }}{% if define.1 %}={{ define.1 }}{% endif %} +{%- for define in group.defines %}+define+{{ define[0] }}{% if define[1] %}={{ define[1] }}{% endif %} {% endfor -%} {%- for incdir in group.incdirs %}+incdir+{{ incdir }} {% endfor -%} diff --git a/src/script_fmt/vivado_tcl.tera b/src/script_fmt/vivado_tcl.tera index d1633f5b..78051646 100644 --- a/src/script_fmt/vivado_tcl.tera +++ b/src/script_fmt/vivado_tcl.tera @@ -30,7 +30,7 @@ set_property include_dirs [list \ {%- for define in all_defines -%} {%- if loop.first %} set_property verilog_define [list \ - {% endif %}{{ define.0 }}{% if define.1 %}={{ define.1 }}{% endif %}{% if loop.last %} \ + {% endif %}{{ define[0] }}{% if define[1] %}={{ define[1] }}{% endif %}{% if loop.last %} \ ] [current_fileset{{ arg }}] {% else %} \ {% endif %}{% endfor %}{% endfor %} diff --git a/src/script_fmt/vsim_tcl.tera b/src/script_fmt/vsim_tcl.tera index 3e1d3b94..dee943fd 100644 --- a/src/script_fmt/vsim_tcl.tera +++ b/src/script_fmt/vsim_tcl.tera @@ -9,7 +9,7 @@ set ROOT "{{ root }}" {%- if group.file_type == 'verilog' %}vlog -incr -sv \ {% for tmp_arg in vlog_args %}{{ tmp_arg }} \ {% endfor -%} -{%- for define in group.defines %}"+define+{{ define.0 }}{% if define.1 %}={{ define.1 }}{% endif %}" \ +{%- for define in group.defines %}"+define+{{ define[0] }}{% if define[1] %}={{ define[1] }}{% endif %}" \ {% endfor -%} {%- for incdir in group.incdirs %}"+incdir+{{ incdir | replace(from=root, to='$ROOT') }}" \ {% endfor -%} @@ -39,7 +39,7 @@ set ROOT "{{ root }}" vlog -incr -sv \ {% for tmp_arg in vlog_args %}{{ tmp_arg }} \ {% endfor -%} -{%- for define in all_defines %}"+define+{{ define.0 }}{% if define.1 %}={{ define.1 }}{% endif %}" \ +{%- for define in all_defines %}"+define+{{ define[0] }}{% if define[1] %}={{ define[1] }}{% endif %}" \ {% endfor -%} {%- for incdir in all_incdirs %}"+incdir+{{ incdir | replace(from=root, to='$ROOT') }}" \ {% endfor -%} From 2f57c7dc0d4db63744c69fa3bce89293989690fa Mon Sep 17 00:00:00 2001 From: Tim Fischer Date: Fri, 31 Jul 2026 16:08:47 +0200 Subject: [PATCH 2/3] CHANGELOG: Add PR links to Tera 2 entries Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1554fe24..88ad212d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Breaking Changes - script: Upgrade the templating engine from Tera 1 to Tera 2. Custom templates passed to `bender script template --template` may need updating. See the [Tera migration guide](https://github.com/Keats/tera/blob/master/MIGRATION.md) for the full list. The output of all built-in formats is unchanged. -### Changed -- `bender clone`: primary flag for the checkout directory is now `--working-dir`, matching `bender snapshot`'s flag for the same concept; `-p`/`--path` are kept as hidden aliases for backwards compatibility. - ### Changed - script: Template errors now report the template name and source location (e.g. `my_format.tera:12:54`), and unknown filters or tests are reported before rendering starts. +- `bender clone`: primary flag for the checkout directory is now `--working-dir`, matching `bender snapshot`'s flag for the same concept; `-p`/`--path` are kept as hidden aliases for backwards compatibility. ## 0.32.1 - 2026-07-07 ### Added From 8da276eab4d60bff33405b3070129bf244928bb6 Mon Sep 17 00:00:00 2001 From: Tim Fischer Date: Fri, 31 Jul 2026 16:13:33 +0200 Subject: [PATCH 3/3] script: Validate built-in templates in the test suite Nothing checked that the shipped templates parse. A broken one only surfaced when somebody ran that particular format, which is how the Tera 1 to Tera 2 upgrade managed to break all eleven at once with CI staying green. `builtin_templates_parse` registers each template the way `emit_template` does. Registering runs Tera's reference validation, so this catches unknown filters, tests and components as well as syntax errors. `builtin_templates_cover_script_fmt_dir` compares the list against `src/script_fmt`, so a new template cannot skip validation by being left out of it. Both were confirmed to fail on a reintroduced `define.0`, on a removed `trim_start_matches` filter, and on an unlisted template file. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 1 + src/cmd/script.rs | 90 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88ad212d..adf848b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - script: Upgrade the templating engine from Tera 1 to Tera 2. Custom templates passed to `bender script template --template` may need updating. See the [Tera migration guide](https://github.com/Keats/tera/blob/master/MIGRATION.md) for the full list. The output of all built-in formats is unchanged. ### Changed + - script: Template errors now report the template name and source location (e.g. `my_format.tera:12:54`), and unknown filters or tests are reported before rendering starts. - `bender clone`: primary flag for the checkout directory is now `--working-dir`, matching `bender snapshot`'s flag for the same concept; `-p`/`--path` are kept as hidden aliases for backwards compatibility. diff --git a/src/cmd/script.rs b/src/cmd/script.rs index 34503591..ecbd0166 100644 --- a/src/cmd/script.rs +++ b/src/cmd/script.rs @@ -1119,3 +1119,93 @@ struct TplSrcStruct { files: IndexSet, file_type: Option, } + +#[cfg(test)] +mod tests { + use super::*; + + /// Every built-in template, embedded exactly as the binary ships it. + /// + /// `builtin_templates_cover_script_fmt_dir` keeps this in sync with `src/script_fmt`, so a + /// newly added template cannot escape validation by being left out of this list. + const BUILTIN_TEMPLATES: &[(&str, &str)] = &[ + ("flist.tera", include_str!("../script_fmt/flist.tera")), + ( + "flist-plus.tera", + include_str!("../script_fmt/flist-plus.tera"), + ), + ("vsim_tcl.tera", include_str!("../script_fmt/vsim_tcl.tera")), + ("vcs_sh.tera", include_str!("../script_fmt/vcs_sh.tera")), + ( + "verilator_sh.tera", + include_str!("../script_fmt/verilator_sh.tera"), + ), + ( + "synopsys_tcl.tera", + include_str!("../script_fmt/synopsys_tcl.tera"), + ), + ( + "formality_tcl.tera", + include_str!("../script_fmt/formality_tcl.tera"), + ), + ( + "riviera_tcl.tera", + include_str!("../script_fmt/riviera_tcl.tera"), + ), + ( + "genus_tcl.tera", + include_str!("../script_fmt/genus_tcl.tera"), + ), + ( + "vivado_tcl.tera", + include_str!("../script_fmt/vivado_tcl.tera"), + ), + ( + "precision_tcl.tera", + include_str!("../script_fmt/precision_tcl.tera"), + ), + ]; + + /// Parse every built-in template the way `emit_template` does. + /// + /// Registering a template runs Tera's reference validation, so this catches unknown filters, + /// tests and components as well as outright syntax errors. Without it a broken template only + /// surfaces when somebody happens to run that one format — which is exactly how the Tera 1 to + /// Tera 2 upgrade managed to break all eleven of them at once. + #[test] + fn builtin_templates_parse() { + for (name, content) in BUILTIN_TEMPLATES { + let mut tera = Tera::default(); + tera.autoescape_on(Vec::<&str>::new()); + if let Err(e) = tera.add_raw_template(name, content) { + panic!("built-in template `{name}` failed to parse: {e}"); + } + } + } + + /// Guard against a template being added to `src/script_fmt` but not to `BUILTIN_TEMPLATES`. + #[test] + fn builtin_templates_cover_script_fmt_dir() { + let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/script_fmt"); + let mut on_disk: Vec = std::fs::read_dir(&dir) + .expect("src/script_fmt must be readable") + .map(|entry| entry.expect("readable dir entry").file_name()) + .filter_map(|name| name.to_str().map(str::to_string)) + .filter(|name| name.ends_with(".tera")) + .collect(); + on_disk.sort(); + + let mut listed: Vec = BUILTIN_TEMPLATES + .iter() + .map(|(name, _)| (*name).to_string()) + .collect(); + listed.sort(); + + assert_eq!( + listed, + on_disk, + "BUILTIN_TEMPLATES is out of sync with {}", + dir.display() + ); + } +}