Compare commits

...

6 Commits

Author SHA1 Message Date
Eric Ratliff
d86c79b9cb All tests pass, all warnings resolved
Some checks failed
CI / Test (Linux) (push) Has been cancelled
CI / Test (Windows MSVC) (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Format (push) Has been cancelled
2026-02-23 13:08:11 -06:00
Eric Ratliff
a517fba88a WSL cargo test is now working 2026-02-23 12:55:28 -06:00
Eric Ratliff
3570777a0d Looking for dependencies on test for a better UX 2026-02-23 12:42:43 -06:00
Eric Ratliff
34d6a765b0 Updated readme
Some checks failed
CI / Test (Linux) (push) Has been cancelled
CI / Test (Windows MSVC) (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Format (push) Has been cancelled
2026-02-23 08:34:10 -06:00
Eric Ratliff
8374cb6079 There was still one occasional problem while running cargo test, it would fail sometimes. Added a serial preprocessor to ensure the underlying arduino command didn't run pipelined. That fixed the problem
Some checks failed
CI / Test (Linux) (push) Has been cancelled
CI / Test (Windows MSVC) (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Format (push) Has been cancelled
2026-02-23 08:17:30 -06:00
Eric Ratliff
7e8d7ecce5 Switched to build.ps1
Some checks failed
CI / Test (Linux) (push) Has been cancelled
CI / Test (Windows MSVC) (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Format (push) Has been cancelled
- Batch script had issues reading from cargo toml file
- Issues were revealed in cargo test (awesome!)
- build.bat is now a wrapper for build.ps1
2026-02-23 07:54:27 -06:00
9 changed files with 592 additions and 314 deletions

148
Cargo.lock generated
View File

@@ -76,6 +76,7 @@ dependencies = [
"predicates", "predicates",
"serde", "serde",
"serde_json", "serde_json",
"serial_test",
"tempfile", "tempfile",
"thiserror", "thiserror",
"toml", "toml",
@@ -261,6 +262,41 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "futures-core"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-executor"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-task"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
name = "futures-util"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"slab",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.17" version = "0.2.17"
@@ -380,6 +416,21 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.8.0" version = "2.8.0"
@@ -419,6 +470,35 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]] [[package]]
name = "predicates" name = "predicates"
version = "3.1.4" version = "3.1.4"
@@ -473,6 +553,15 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.4.6" version = "0.4.6"
@@ -539,6 +628,27 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "scc"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc"
dependencies = [
"sdd",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sdd"
version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.228" version = "1.0.228"
@@ -591,6 +701,44 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serial_test"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f"
dependencies = [
"futures-executor",
"futures-util",
"log",
"once_cell",
"parking_lot",
"scc",
"serial_test_derive",
]
[[package]]
name = "serial_test_derive"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.11.1" version = "0.11.1"

View File

@@ -47,6 +47,7 @@ tempfile = "3.13"
[dev-dependencies] [dev-dependencies]
assert_cmd = "2.0" assert_cmd = "2.0"
predicates = "3.1" predicates = "3.1"
serial_test = "3"
[profile.release] [profile.release]
opt-level = 3 opt-level = 3

View File

@@ -259,6 +259,7 @@ your-project/
test_tmp36.cpp Library driver tests test_tmp36.cpp Library driver tests
CMakeLists.txt Fetches Google Test, compiles tests CMakeLists.txt Fetches Google Test, compiles tests
build.sh / build.bat Compile sketch build.sh / build.bat Compile sketch
build.sh / build.bat / build.ps1 Compile sketch (bat wraps ps1)
upload.sh / upload.bat Compile + upload to board upload.sh / upload.bat Compile + upload to board
monitor.sh / monitor.bat Serial monitor monitor.sh / monitor.bat Serial monitor
test.sh / test.bat Run host-side tests test.sh / test.bat Run host-side tests
@@ -435,17 +436,38 @@ Upload these to a Gitea release. The script requires `build-essential`,
`mingw-w64`, and `zip` as described above. `mingw-w64`, and `zip` as described above.
### Running the test suite ### Running the test suite
```bash ```bash
cargo test cargo test
``` ```
650 tests (137 unit + 506 integration + 7 end-to-end), zero warnings. The e2e The test suite covers unit, integration, and end-to-end scenarios. The e2e
tests generate real projects and compile their C++ test suites, catching tests generate real projects and compile their C++ test suites, catching
build-system issues like missing linker flags and include paths. They require build-system issues like missing linker flags and include paths. They require
cmake and a C++ compiler; if those tools are not installed, the compile tests cmake and a C++ compiler; if those tools are not installed, the compile tests
skip gracefully and everything else still passes. skip gracefully and everything else still passes.
#### Full test suite on Linux / WSL
The e2e tests need `cmake`, `g++`, and `arduino-cli` with the AVR core:
```bash
sudo apt install cmake g++
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh
sudo mv bin/arduino-cli /usr/local/bin/
arduino-cli core install arduino:avr
```
#### Full test suite on Windows
Install [arduino-cli](https://arduino.github.io/arduino-cli/installation/)
and add it to your PATH, then install the AVR core:
```
arduino-cli core install arduino:avr
```
CMake and a C++ compiler are needed for the host-side test compilation.
Install [CMake](https://cmake.org/download/) and either MinGW-w64 or open
a Visual Studio Developer Command Prompt (which provides `cl.exe`).
--- ---
## License ## License

95
build.rs Normal file
View File

@@ -0,0 +1,95 @@
// build.rs -- Compile-time detection of optional build tools.
//
// Sets cfg flags that integration tests use to gracefully skip when
// tools are missing instead of panicking with scary error messages.
//
// has_cmake cmake is in PATH
// has_cpp_compiler g++, clang++, or cl is in PATH
// has_git git is in PATH
// has_arduino_cli arduino-cli is in PATH
//
// Usage in tests:
// #[cfg_attr(not(has_cmake), ignore = "cmake not found")]
use std::process::{Command, Stdio};
/// Check if a tool is available by running it with --version.
/// Works for most tools (cmake, git, g++, clang++, arduino-cli).
fn has_tool(name: &str) -> bool {
Command::new(name)
.arg("--version")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.is_ok()
}
/// Check if a C++ compiler is available.
///
/// g++ and clang++ respond to --version normally. MSVC's cl.exe does
/// not -- it rejects --version and returns an error. On Windows we
/// fall back to checking PATH via `where cl`. On Unix, cmake cannot
/// discover MSVC so we only check g++ and clang++.
fn has_cpp_compiler() -> bool {
if has_tool("g++") || has_tool("clang++") {
return true;
}
// cl.exe doesn't support --version; check PATH directly on Windows.
// On a regular command prompt cl may not be in PATH, but cmake can
// still find MSVC via the Visual Studio registry. We check `where`
// as a best-effort signal.
#[cfg(windows)]
{
let found = Command::new("where")
.arg("cl")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false);
if found {
return true;
}
// Last resort: cmake can discover MSVC even when cl is not in
// PATH. Check if any Visual Studio installation exists by
// looking for vswhere, which ships with VS 2017+.
let vswhere = Command::new("cmd")
.args(["/C", "where", "vswhere"])
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false);
if vswhere {
return true;
}
}
#[cfg(not(windows))]
let _ = (); // silence unused warning
false
}
fn main() {
// Declare custom cfg names so rustc doesn't warn about them.
println!("cargo::rustc-check-cfg=cfg(has_cmake)");
println!("cargo::rustc-check-cfg=cfg(has_cpp_compiler)");
println!("cargo::rustc-check-cfg=cfg(has_git)");
println!("cargo::rustc-check-cfg=cfg(has_arduino_cli)");
if has_tool("cmake") {
println!("cargo:rustc-cfg=has_cmake");
}
if has_cpp_compiler() {
println!("cargo:rustc-cfg=has_cpp_compiler");
}
if has_tool("git") {
println!("cargo:rustc-cfg=has_git");
}
if has_tool("arduino-cli") {
println!("cargo:rustc-cfg=has_arduino_cli");
}
}

View File

@@ -1,9 +1,11 @@
@echo off @echo off
setlocal enabledelayedexpansion :: build.bat -- Thin wrapper that invokes build.ps1
:: build.bat -- Compile the sketch using arduino-cli
:: ::
:: Reads all settings from .anvil.toml. No Anvil binary required. :: Students can type "build" at a command prompt or double-click this file.
:: All logic lives in build.ps1. Requires PowerShell 5.1+ (ships with
:: Windows 10/11).
::
:: Settings are read from .anvil.toml. No Anvil binary required.
:: ::
:: Usage: :: Usage:
:: build.bat Compile (verify only) :: build.bat Compile (verify only)
@@ -11,173 +13,5 @@ setlocal enabledelayedexpansion
:: build.bat --clean Delete build cache first :: build.bat --clean Delete build cache first
:: build.bat --verbose Show full compiler output :: build.bat --verbose Show full compiler output
set "SCRIPT_DIR=%~dp0" powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0build.ps1" %*
set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%" exit /b %ERRORLEVEL%
set "CONFIG=%SCRIPT_DIR%\.anvil.toml"
if not exist "%CONFIG%" (
echo FAIL: No .anvil.toml found in %SCRIPT_DIR%
exit /b 1
)
:: -- Parse .anvil.toml (flat keys) ----------------------------------------
for /f "usebackq tokens=1,* delims==" %%a in ("%CONFIG%") do (
set "_K=%%a"
if not "!_K:~0,1!"=="#" if not "!_K:~0,1!"=="[" (
set "_K=!_K: =!"
set "_V=%%b"
if defined _V (
set "_V=!_V: =!"
set "_V=!_V:"=!"
)
if "!_K!"=="name" set "SKETCH_NAME=!_V!"
if "!_K!"=="default" set "DEFAULT_BOARD=!_V!"
if "!_K!"=="warnings" set "WARNINGS=!_V!"
)
)
if "%SKETCH_NAME%"=="" (
echo FAIL: Could not read project name from .anvil.toml
exit /b 1
)
set "SKETCH_DIR=%SCRIPT_DIR%\%SKETCH_NAME%"
set "BUILD_DIR=%SCRIPT_DIR%\.build"
:: -- Parse arguments ------------------------------------------------------
set "DO_CLEAN=0"
set "VERBOSE="
set "BOARD_NAME="
:parse_args
if "%~1"=="" goto done_args
if "%~1"=="--board" set "BOARD_NAME=%~2" & shift & shift & goto parse_args
if "%~1"=="--clean" set "DO_CLEAN=1" & shift & goto parse_args
if "%~1"=="--verbose" set "VERBOSE=--verbose" & shift & goto parse_args
if "%~1"=="--help" goto show_help
if "%~1"=="-h" goto show_help
echo FAIL: Unknown option: %~1
exit /b 1
:show_help
echo Usage: build.bat [--board NAME] [--clean] [--verbose]
echo Compiles the sketch. Settings from .anvil.toml.
echo --board NAME selects a board from [boards.NAME].
exit /b 0
:done_args
:: -- Resolve board --------------------------------------------------------
if "%BOARD_NAME%"=="" set "BOARD_NAME=%DEFAULT_BOARD%"
if "%BOARD_NAME%"=="" (
echo FAIL: No default board set in .anvil.toml.
echo.
echo Add a default to the [build] section of .anvil.toml:
echo default = "uno"
echo.
echo And make sure a matching [boards.uno] section exists:
echo [boards.uno]
echo fqbn = "arduino:avr:uno"
echo.
echo Or with Anvil: anvil board --default uno
echo List boards: anvil board --listall
echo arduino-cli board listall
exit /b 1
)
set "BOARD_SECTION=[boards.%BOARD_NAME%]"
set "IN_SECTION=0"
set "FQBN="
for /f "usebackq tokens=*" %%L in ("%CONFIG%") do (
set "_LINE=%%L"
if "!_LINE!"=="!BOARD_SECTION!" (
set "IN_SECTION=1"
) else if "!IN_SECTION!"=="1" (
if "!_LINE:~0,1!"=="[" (
set "IN_SECTION=0"
) else if not "!_LINE:~0,1!"=="#" (
for /f "tokens=1,* delims==" %%a in ("!_LINE!") do (
set "_BK=%%a"
set "_BK=!_BK: =!"
set "_BV=%%b"
if defined _BV (
set "_BV=!_BV: =!"
set "_BV=!_BV:"=!"
)
if "!_BK!"=="fqbn" set "FQBN=!_BV!"
)
)
)
)
if "!FQBN!"=="" (
echo FAIL: No [boards.%BOARD_NAME%] section in .anvil.toml.
echo.
echo Add it to .anvil.toml:
echo [boards.%BOARD_NAME%]
echo fqbn = "arduino:avr:uno" ^(replace with your board^)
echo.
echo Or with Anvil: anvil board --add %BOARD_NAME%
echo List boards: anvil board --listall
echo arduino-cli board listall
exit /b 1
)
if not "%BOARD_NAME%"=="%DEFAULT_BOARD%" (
echo ok Using board: %BOARD_NAME% -- %FQBN%
)
:: -- Preflight ------------------------------------------------------------
where arduino-cli >nul 2>nul
if errorlevel 1 (
echo FAIL: arduino-cli not found in PATH.
exit /b 1
)
if not exist "%SKETCH_DIR%" (
echo FAIL: Sketch directory not found: %SKETCH_DIR%
exit /b 1
)
:: -- Clean ----------------------------------------------------------------
if "%DO_CLEAN%"=="1" (
if exist "%BUILD_DIR%" (
echo Cleaning build cache...
rmdir /s /q "%BUILD_DIR%"
)
)
:: -- Build include flags --------------------------------------------------
set "BUILD_FLAGS="
for %%d in (lib\hal lib\app) do (
if exist "%SCRIPT_DIR%\%%d" (
set "BUILD_FLAGS=!BUILD_FLAGS! -I%SCRIPT_DIR%\%%d"
)
)
:: Auto-discover driver libraries (added by: anvil add <driver>)
if exist "%SCRIPT_DIR%\lib\drivers" (
for /d %%d in ("%SCRIPT_DIR%\lib\drivers\*") do (
set "BUILD_FLAGS=!BUILD_FLAGS! -I%%d"
)
)
set "BUILD_FLAGS=!BUILD_FLAGS! -Werror"
:: -- Compile --------------------------------------------------------------
echo Compiling %SKETCH_NAME%...
echo Board: %FQBN%
echo Sketch: %SKETCH_DIR%
echo.
if not exist "%BUILD_DIR%" mkdir "%BUILD_DIR%"
arduino-cli compile --fqbn %FQBN% --build-path "%BUILD_DIR%" --warnings %WARNINGS% --build-property "compiler.cpp.extra_flags=%BUILD_FLAGS%" --build-property "compiler.c.extra_flags=%BUILD_FLAGS%" %VERBOSE% "%SKETCH_DIR%"
if errorlevel 1 (
echo.
echo FAIL: Compilation failed.
exit /b 1
)
echo.
echo ok Compile succeeded.
echo.

212
templates/basic/build.ps1 Normal file
View File

@@ -0,0 +1,212 @@
# build.ps1 -- Compile the sketch using arduino-cli
#
# Reads all settings from .anvil.toml. No Anvil binary required.
# Called by build.bat (thin wrapper) or directly:
# powershell -File build.ps1 [--board NAME] [--clean] [--verbose]
#
# Exit codes: 0 = success, 1 = error
param(
[string]$board = "",
[switch]$clean,
[switch]$verbose,
[switch]$help
)
$ErrorActionPreference = "Stop"
# -- Helpers ---------------------------------------------------------------
function Fail($msg) {
Write-Host "FAIL: $msg" -ForegroundColor Red
exit 1
}
function Ok($msg) {
Write-Host "ok $msg" -ForegroundColor Green
}
# -- Locate config ---------------------------------------------------------
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
$Config = Join-Path $ScriptDir ".anvil.toml"
if (-not (Test-Path $Config)) {
Fail "No .anvil.toml found in $ScriptDir"
}
# -- Parse .anvil.toml -----------------------------------------------------
# Simple line-by-line parser. Tracks current section header to support
# [project], [build], [boards.NAME], etc.
$tomlData = @{}
$currentSection = ""
foreach ($rawLine in Get-Content $Config) {
$line = $rawLine.Trim()
# Skip blank lines and comments
if ($line -eq "" -or $line.StartsWith("#")) { continue }
# Section header: [project], [boards.uno], etc.
if ($line -match '^\[(.+)\]$') {
$currentSection = $Matches[1]
continue
}
# Key = value (only process lines with =)
if ($line -match '^([^=]+?)\s*=\s*(.+)$') {
$key = $Matches[1].Trim()
$val = $Matches[2].Trim()
# Strip surrounding quotes
if ($val.StartsWith('"') -and $val.EndsWith('"')) {
$val = $val.Substring(1, $val.Length - 2)
}
# Skip array values (multi-line or inline [...])
if ($val.StartsWith("[")) { continue }
$fullKey = if ($currentSection) { "$currentSection.$key" } else { $key }
$tomlData[$fullKey] = $val
}
}
# -- Extract settings ------------------------------------------------------
$SketchName = $tomlData["project.name"]
$DefaultBoard = $tomlData["build.default"]
$Warnings = $tomlData["build.warnings"]
if (-not $SketchName) {
Fail "Could not read project name from .anvil.toml"
}
$SketchDir = Join-Path $ScriptDir $SketchName
$BuildDir = Join-Path $ScriptDir ".build"
# -- Help ------------------------------------------------------------------
if ($help) {
Write-Host "Usage: build.bat [--board NAME] [--clean] [--verbose]"
Write-Host " Compiles the sketch. Settings from .anvil.toml."
Write-Host " --board NAME selects a board from [boards.NAME]."
exit 0
}
# -- Resolve board ---------------------------------------------------------
$BoardName = if ($board) { $board } else { $DefaultBoard }
if (-not $BoardName) {
Fail @"
No default board set in .anvil.toml.
Add a default to the [build] section of .anvil.toml:
default = "uno"
And make sure a matching [boards.uno] section exists:
[boards.uno]
fqbn = "arduino:avr:uno"
Or with Anvil: Anvil board --default uno
List boards: Anvil board --listall
arduino-cli board listall
"@
}
$Fqbn = $tomlData["boards.$BoardName.fqbn"]
if (-not $Fqbn) {
Fail @"
No [boards.$BoardName] section in .anvil.toml.
Add it to .anvil.toml:
[boards.$BoardName]
fqbn = "arduino:avr:uno" (replace with your board)
Or with Anvil: Anvil board --add $BoardName
List boards: Anvil board --listall
arduino-cli board listall
"@
}
if ($BoardName -ne $DefaultBoard) {
Ok "Using board: $BoardName -- $Fqbn"
}
# -- Preflight -------------------------------------------------------------
$arduinoCli = Get-Command "arduino-cli" -ErrorAction SilentlyContinue
if (-not $arduinoCli) {
Fail "arduino-cli not found in PATH."
}
if (-not (Test-Path $SketchDir)) {
Fail "Sketch directory not found: $SketchDir"
}
# -- Clean -----------------------------------------------------------------
if ($clean -and (Test-Path $BuildDir)) {
Write-Host "Cleaning build cache..."
Remove-Item -Recurse -Force $BuildDir
}
# -- Build include flags ---------------------------------------------------
$buildFlags = @()
foreach ($sub in @("lib\hal", "lib\app")) {
$dir = Join-Path $ScriptDir $sub
if (Test-Path $dir) {
$buildFlags += "-I$dir"
}
}
# Auto-discover driver libraries (added by: anvil add <driver>)
$driversDir = Join-Path $ScriptDir "lib\drivers"
if (Test-Path $driversDir) {
foreach ($d in Get-ChildItem -Path $driversDir -Directory) {
$buildFlags += "-I$($d.FullName)"
}
}
$buildFlags += "-Werror"
$flagsStr = $buildFlags -join " "
# -- Compile ---------------------------------------------------------------
Write-Host "Compiling $SketchName..."
Write-Host " Board: $Fqbn"
Write-Host " Sketch: $SketchDir"
Write-Host ""
if (-not (Test-Path $BuildDir)) {
New-Item -ItemType Directory -Path $BuildDir | Out-Null
}
$compileArgs = @(
"compile"
"--fqbn", $Fqbn
"--build-path", $BuildDir
"--warnings", $Warnings
"--build-property", "compiler.cpp.extra_flags=$flagsStr"
"--build-property", "compiler.c.extra_flags=$flagsStr"
)
if ($verbose) {
$compileArgs += "--verbose"
}
$compileArgs += $SketchDir
& arduino-cli @compileArgs
if ($LASTEXITCODE -ne 0) {
Write-Host ""
Fail "Compilation failed."
}
Write-Host ""
Ok "Compile succeeded."
Write-Host ""
exit 0

View File

@@ -9,8 +9,10 @@
// test.sh / test/run_tests.sh --> cmake, g++ (or clang++), git // test.sh / test/run_tests.sh --> cmake, g++ (or clang++), git
// build.sh --> arduino-cli (with arduino:avr core) // build.sh --> arduino-cli (with arduino:avr core)
// //
// If any dependency is missing the test FAILS -- that is intentional. // If a dependency is missing, the test is SKIPPED (shown as "ignored"
// A build machine that ships Anvil binaries MUST have these tools. // in cargo test output). Detection happens at compile time via build.rs
// which sets cfg flags: has_cmake, has_cpp_compiler, has_git,
// has_arduino_cli.
// //
// On Windows the .bat variants are tested instead. // On Windows the .bat variants are tested instead.
// ========================================================================== // ==========================================================================
@@ -19,6 +21,7 @@ use std::fs;
use std::path::Path; use std::path::Path;
use std::process::Command; use std::process::Command;
use tempfile::TempDir; use tempfile::TempDir;
use serial_test::serial;
use anvil::templates::{TemplateContext, TemplateManager}; use anvil::templates::{TemplateContext, TemplateManager};
use anvil::version::ANVIL_VERSION; use anvil::version::ANVIL_VERSION;
@@ -32,7 +35,7 @@ fn test_context(name: &str) -> TemplateContext {
TemplateContext { TemplateContext {
project_name: name.to_string(), project_name: name.to_string(),
anvil_version: ANVIL_VERSION.to_string(), anvil_version: ANVIL_VERSION.to_string(),
board_name: "Arduino Uno (ATmega328P)".to_string(), board_name: "uno".to_string(), // <-- was "Arduino Uno (ATmega328P)"
fqbn: "arduino:avr:uno".to_string(), fqbn: "arduino:avr:uno".to_string(),
baud: 115200, baud: 115200,
} }
@@ -121,91 +124,6 @@ fn run_script_with_args(dir: &Path, script: &str, args: &[&str]) -> (bool, Strin
(output.status.success(), stdout, stderr) (output.status.success(), stdout, stderr)
} }
/// Assert that a command-line tool is available in PATH.
/// Panics with a clear message if not found.
fn require_tool(name: &str) {
let check = if cfg!(windows) {
Command::new("where").arg(name).output()
} else {
Command::new("which").arg(name).output()
};
match check {
Ok(output) if output.status.success() => {}
_ => panic!(
"\n\n\
===================================================================\n\
MISSING BUILD DEPENDENCY: {name}\n\
===================================================================\n\
\n\
Anvil's cargo tests REQUIRE build-machine dependencies.\n\
Install '{name}' and re-run. See 'anvil doctor' for guidance.\n\
\n\
===================================================================\n"
),
}
}
/// Check that at least one C++ compiler is present.
///
/// On Windows, cmake discovers MSVC through the Visual Studio installation
/// even when cl.exe is not directly in PATH, so we check for cl.exe as
/// well as g++ and clang++. If none are found in PATH we still let cmake
/// try -- it will fail at configure time with a clear message.
fn require_cpp_compiler() {
let check_tool = |name: &str| -> bool {
Command::new(if cfg!(windows) { "where" } else { "which" })
.arg(name)
.output()
.map(|o| o.status.success())
.unwrap_or(false)
};
let has_gpp = check_tool("g++");
let has_clangpp = check_tool("clang++");
let has_cl = if cfg!(windows) { check_tool("cl") } else { false };
// On Windows, cmake can discover MSVC even when cl.exe is not in
// the current PATH (via vswhere / VS installation registry). So
// we only hard-fail on Linux/macOS where the compiler really must
// be in PATH.
if !has_gpp && !has_clangpp && !has_cl {
if cfg!(windows) {
// Warn but don't panic -- cmake will try to find MSVC
eprintln!(
"\n\
WARNING: No C++ compiler (g++, clang++, cl) found in PATH.\n\
cmake may still find MSVC via Visual Studio installation.\n\
If tests fail, open a VS Developer Command Prompt or install\n\
Build Tools for Visual Studio.\n"
);
} else {
panic!(
"\n\n\
===================================================================\n\
MISSING BUILD DEPENDENCY: C++ compiler (g++ or clang++)\n\
===================================================================\n\
\n\
Install g++ or clang++ and re-run.\n\
\n\
===================================================================\n"
);
}
}
}
/// Require cmake + C++ compiler + git (the test script prereqs).
fn require_test_script_deps() {
require_tool("cmake");
require_tool("git");
require_cpp_compiler();
}
/// Require arduino-cli (the build script prereqs).
fn require_build_script_deps() {
require_tool("arduino-cli");
}
/// Platform-appropriate script paths. /// Platform-appropriate script paths.
fn root_test_script() -> &'static str { fn root_test_script() -> &'static str {
if cfg!(windows) { "test.bat" } else { "test.sh" } if cfg!(windows) { "test.bat" } else { "test.sh" }
@@ -282,9 +200,10 @@ fn find_file_recursive(dir: &Path, prefix: &str) -> bool {
// ========================================================================== // ==========================================================================
#[test] #[test]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
fn test_root_test_script_executes_successfully() { fn test_root_test_script_executes_successfully() {
require_test_script_deps();
let tmp = extract_project("root_test"); let tmp = extract_project("root_test");
#[cfg(unix)] #[cfg(unix)]
@@ -305,9 +224,10 @@ fn test_root_test_script_executes_successfully() {
} }
#[test] #[test]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
fn test_root_test_script_tests_actually_ran() { fn test_root_test_script_tests_actually_ran() {
require_test_script_deps();
let tmp = extract_project("root_verify"); let tmp = extract_project("root_verify");
#[cfg(unix)] #[cfg(unix)]
@@ -337,9 +257,10 @@ fn test_root_test_script_tests_actually_ran() {
} }
#[test] #[test]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
fn test_root_test_script_idempotent() { fn test_root_test_script_idempotent() {
require_test_script_deps();
let tmp = extract_project("root_idem"); let tmp = extract_project("root_idem");
#[cfg(unix)] #[cfg(unix)]
@@ -366,9 +287,10 @@ fn test_root_test_script_idempotent() {
// ========================================================================== // ==========================================================================
#[test] #[test]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
fn test_inner_run_tests_script_executes_successfully() { fn test_inner_run_tests_script_executes_successfully() {
require_test_script_deps();
let tmp = extract_project("inner_test"); let tmp = extract_project("inner_test");
#[cfg(unix)] #[cfg(unix)]
@@ -389,9 +311,10 @@ fn test_inner_run_tests_script_executes_successfully() {
} }
#[test] #[test]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
fn test_inner_run_tests_google_tests_actually_ran() { fn test_inner_run_tests_google_tests_actually_ran() {
require_test_script_deps();
let tmp = extract_project("inner_gtest"); let tmp = extract_project("inner_gtest");
#[cfg(unix)] #[cfg(unix)]
@@ -420,9 +343,10 @@ fn test_inner_run_tests_google_tests_actually_ran() {
} }
#[test] #[test]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
fn test_inner_run_tests_clean_flag_rebuilds() { fn test_inner_run_tests_clean_flag_rebuilds() {
require_test_script_deps();
let tmp = extract_project("inner_clean"); let tmp = extract_project("inner_clean");
#[cfg(unix)] #[cfg(unix)]
@@ -456,9 +380,10 @@ fn test_inner_run_tests_clean_flag_rebuilds() {
} }
#[test] #[test]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
fn test_inner_run_tests_produces_test_binary() { fn test_inner_run_tests_produces_test_binary() {
require_test_script_deps();
let tmp = extract_project("inner_bin"); let tmp = extract_project("inner_bin");
#[cfg(unix)] #[cfg(unix)]
@@ -481,9 +406,10 @@ fn test_inner_run_tests_produces_test_binary() {
} }
#[test] #[test]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
fn test_inner_run_tests_idempotent() { fn test_inner_run_tests_idempotent() {
require_test_script_deps();
let tmp = extract_project("inner_idem"); let tmp = extract_project("inner_idem");
#[cfg(unix)] #[cfg(unix)]
@@ -511,9 +437,9 @@ fn test_inner_run_tests_idempotent() {
// ========================================================================== // ==========================================================================
#[test] #[test]
#[serial]
#[cfg_attr(not(has_arduino_cli), ignore = "arduino-cli not found")]
fn test_build_script_compiles_sketch() { fn test_build_script_compiles_sketch() {
require_build_script_deps();
let tmp = extract_project("build_test"); let tmp = extract_project("build_test");
#[cfg(unix)] #[cfg(unix)]
@@ -534,9 +460,9 @@ fn test_build_script_compiles_sketch() {
} }
#[test] #[test]
#[serial]
#[cfg_attr(not(has_arduino_cli), ignore = "arduino-cli not found")]
fn test_build_script_produces_compilation_output() { fn test_build_script_produces_compilation_output() {
require_build_script_deps();
let tmp = extract_project("compile_out"); let tmp = extract_project("compile_out");
#[cfg(unix)] #[cfg(unix)]
@@ -566,9 +492,9 @@ fn test_build_script_produces_compilation_output() {
} }
#[test] #[test]
#[serial]
#[cfg_attr(not(has_arduino_cli), ignore = "arduino-cli not found")]
fn test_build_script_idempotent() { fn test_build_script_idempotent() {
require_build_script_deps();
let tmp = extract_project("build_idem"); let tmp = extract_project("build_idem");
#[cfg(unix)] #[cfg(unix)]
@@ -592,10 +518,12 @@ fn test_build_script_idempotent() {
// ========================================================================== // ==========================================================================
#[test] #[test]
#[serial]
#[cfg_attr(not(has_cmake), ignore = "cmake not found")]
#[cfg_attr(not(has_cpp_compiler), ignore = "C++ compiler not found")]
#[cfg_attr(not(has_git), ignore = "git not found")]
#[cfg_attr(not(has_arduino_cli), ignore = "arduino-cli not found")]
fn test_full_project_all_scripts_pass() { fn test_full_project_all_scripts_pass() {
require_test_script_deps();
require_build_script_deps();
let tmp = extract_project("full_e2e"); let tmp = extract_project("full_e2e");
#[cfg(unix)] #[cfg(unix)]
@@ -749,10 +677,12 @@ fn test_cmake_lists_fetches_google_test() {
fn test_scripts_all_reference_anvil_toml() { fn test_scripts_all_reference_anvil_toml() {
let tmp = extract_project("toml_refs"); let tmp = extract_project("toml_refs");
// Build and upload scripts must read .anvil.toml for configuration // Build and upload scripts must read .anvil.toml for configuration.
// On Windows, build.bat is a thin wrapper that calls build.ps1,
// so we check the .ps1 file for content.
let config_scripts = vec![ let config_scripts = vec![
"build.sh", "build.sh",
"build.bat", "build.ps1",
"upload.sh", "upload.sh",
"upload.bat", "upload.bat",
]; ];
@@ -777,9 +707,11 @@ fn test_scripts_all_reference_anvil_toml() {
fn test_scripts_invoke_arduino_cli_not_anvil() { fn test_scripts_invoke_arduino_cli_not_anvil() {
let tmp = extract_project("no_anvil_dep"); let tmp = extract_project("no_anvil_dep");
// Build/upload/monitor scripts must invoke arduino-cli directly // Build/upload/monitor scripts must invoke arduino-cli directly.
// On Windows, build.bat is a thin wrapper calling build.ps1,
// so we check the .ps1 file for content.
let scripts = vec![ let scripts = vec![
"build.sh", "build.bat", "build.sh", "build.ps1",
"upload.sh", "upload.bat", "upload.sh", "upload.bat",
"monitor.sh", "monitor.bat", "monitor.sh", "monitor.bat",
]; ];
@@ -829,6 +761,10 @@ fn test_scripts_invoke_arduino_cli_not_anvil() {
|| trimmed.starts_with("Write-Host") || trimmed.starts_with("Write-Host")
|| trimmed.starts_with("Write-Error") || trimmed.starts_with("Write-Error")
|| trimmed.starts_with("Write-Warning") || trimmed.starts_with("Write-Warning")
|| trimmed.starts_with("Fail ")
|| trimmed.starts_with("Fail(")
|| trimmed.starts_with("Fail \"")
|| trimmed.starts_with("Fail @")
{ {
return false; return false;
} }
@@ -860,6 +796,7 @@ fn test_all_expected_scripts_exist() {
let expected = vec![ let expected = vec![
"build.sh", "build.sh",
"build.bat", "build.bat",
"build.ps1",
"upload.sh", "upload.sh",
"upload.bat", "upload.bat",
"monitor.sh", "monitor.sh",

View File

@@ -574,7 +574,9 @@ fn test_sh_scripts_have_toml_section_get() {
#[test] #[test]
fn test_bat_scripts_have_section_parser() { fn test_bat_scripts_have_section_parser() {
// Batch scripts need section-aware TOML parsing for board profiles // Windows scripts need section-aware TOML parsing for board profiles.
// build.bat delegates to build.ps1; upload.bat and monitor.bat may
// still use batch-native parsing or their own .ps1 backends.
let tmp = TempDir::new().unwrap(); let tmp = TempDir::new().unwrap();
let ctx = TemplateContext { let ctx = TemplateContext {
project_name: "bat_section".to_string(), project_name: "bat_section".to_string(),
@@ -585,12 +587,33 @@ fn test_bat_scripts_have_section_parser() {
}; };
TemplateManager::extract("basic", tmp.path(), &ctx).unwrap(); TemplateManager::extract("basic", tmp.path(), &ctx).unwrap();
for bat in &["build.bat", "upload.bat", "monitor.bat"] { // Check each Windows script OR its PowerShell backend for section parsing
let content = fs::read_to_string(tmp.path().join(bat)).unwrap(); let pairs: &[(&str, &str)] = &[
("build.bat", "build.ps1"),
("upload.bat", "upload.ps1"),
("monitor.bat", "monitor.ps1"),
];
for (bat, ps1) in pairs {
let bat_path = tmp.path().join(bat);
let ps1_path = tmp.path().join(ps1);
let has_parser = if ps1_path.exists() {
// PowerShell backend handles TOML parsing
let content = fs::read_to_string(&ps1_path).unwrap();
content.contains("boards.") || content.contains("currentSection")
} else if bat_path.exists() {
// Batch does its own section parsing
let content = fs::read_to_string(&bat_path).unwrap();
content.contains("BOARD_SECTION") || content.contains("IN_SECTION")
} else {
false
};
assert!( assert!(
content.contains("BOARD_SECTION") || content.contains("IN_SECTION"), has_parser,
"{} should have section parser for board profiles", "{} (or {}) should have section parser for board profiles",
bat bat, ps1
); );
} }
} }

View File

@@ -489,7 +489,7 @@ fn test_refresh_freshly_extracted_is_up_to_date() {
TemplateManager::extract("basic", reference.path(), &ctx).unwrap(); TemplateManager::extract("basic", reference.path(), &ctx).unwrap();
let refreshable = vec![ let refreshable = vec![
"build.sh", "build.bat", "build.sh", "build.bat", "build.ps1",
"upload.sh", "upload.bat", "upload.sh", "upload.bat",
"monitor.sh", "monitor.bat", "monitor.sh", "monitor.bat",
"test.sh", "test.bat", "test.sh", "test.bat",
@@ -561,7 +561,7 @@ fn test_refresh_does_not_list_user_files() {
]; ];
let refreshable = vec![ let refreshable = vec![
"build.sh", "build.bat", "build.sh", "build.bat", "build.ps1",
"upload.sh", "upload.bat", "upload.sh", "upload.bat",
"monitor.sh", "monitor.bat", "monitor.sh", "monitor.bat",
"test.sh", "test.bat", "test.sh", "test.bat",
@@ -644,12 +644,14 @@ fn test_scripts_read_default_board() {
); );
} }
for bat in &["build.bat", "upload.bat", "monitor.bat"] { // build.bat is now a thin wrapper; build.ps1 has the real logic.
let content = fs::read_to_string(tmp.path().join(bat)).unwrap(); // upload.bat and monitor.bat still have batch-native parsing.
for script in &["build.ps1", "upload.bat", "monitor.bat"] {
let content = fs::read_to_string(tmp.path().join(script)).unwrap();
assert!( assert!(
content.contains("DEFAULT_BOARD"), content.contains("DEFAULT_BOARD") || content.contains("DefaultBoard") || content.contains("default"),
"{} should read default field into DEFAULT_BOARD", "{} should read default field for board selection",
bat script
); );
} }
} }
@@ -674,8 +676,9 @@ fn test_scripts_use_compiler_extra_flags_not_build() {
}; };
TemplateManager::extract("basic", tmp.path(), &ctx).unwrap(); TemplateManager::extract("basic", tmp.path(), &ctx).unwrap();
// build.bat is a thin wrapper; check build.ps1 for content
let compile_scripts = vec![ let compile_scripts = vec![
"build.sh", "build.bat", "build.sh", "build.ps1",
"upload.sh", "upload.bat", "upload.sh", "upload.bat",
]; ];
@@ -743,8 +746,9 @@ fn test_script_errors_show_manual_fix() {
}; };
TemplateManager::extract("basic", tmp.path(), &ctx).unwrap(); TemplateManager::extract("basic", tmp.path(), &ctx).unwrap();
// build.bat is a thin wrapper; check build.ps1 for content
let all_scripts = vec![ let all_scripts = vec![
"build.sh", "build.bat", "build.sh", "build.ps1",
"upload.sh", "upload.bat", "upload.sh", "upload.bat",
"monitor.sh", "monitor.bat", "monitor.sh", "monitor.bat",
]; ];
@@ -752,7 +756,7 @@ fn test_script_errors_show_manual_fix() {
for script in &all_scripts { for script in &all_scripts {
let content = fs::read_to_string(tmp.path().join(script)).unwrap(); let content = fs::read_to_string(tmp.path().join(script)).unwrap();
assert!( assert!(
content.contains("default = "), content.contains("default = ") || content.contains("default ="),
"{} error messages should show the manual fix (default = \"...\")", "{} error messages should show the manual fix (default = \"...\")",
script script
); );
@@ -773,8 +777,9 @@ fn test_script_errors_mention_arduino_cli() {
}; };
TemplateManager::extract("basic", tmp.path(), &ctx).unwrap(); TemplateManager::extract("basic", tmp.path(), &ctx).unwrap();
// build.bat is a thin wrapper; check build.ps1 for content
let all_scripts = vec![ let all_scripts = vec![
"build.sh", "build.bat", "build.sh", "build.ps1",
"upload.sh", "upload.bat", "upload.sh", "upload.bat",
"monitor.sh", "monitor.bat", "monitor.sh", "monitor.bat",
]; ];
@@ -803,8 +808,8 @@ fn test_script_errors_mention_toml_section_syntax() {
}; };
TemplateManager::extract("basic", tmp.path(), &ctx).unwrap(); TemplateManager::extract("basic", tmp.path(), &ctx).unwrap();
// build and upload scripts have both no-default and board-not-found errors // build.bat is a thin wrapper; check build.ps1 for content
for script in &["build.sh", "build.bat", "upload.sh", "upload.bat"] { for script in &["build.sh", "build.ps1", "upload.sh", "upload.bat"] {
let content = fs::read_to_string(tmp.path().join(script)).unwrap(); let content = fs::read_to_string(tmp.path().join(script)).unwrap();
assert!( assert!(
content.contains("[boards."), content.contains("[boards."),
@@ -932,11 +937,12 @@ fn test_build_scripts_autodiscover_driver_includes() {
}; };
TemplateManager::extract("basic", tmp.path(), &ctx).unwrap(); TemplateManager::extract("basic", tmp.path(), &ctx).unwrap();
// build.bat is a thin wrapper; check build.ps1 for content.
// All four compile scripts must auto-discover lib/drivers/* // All four compile scripts must auto-discover lib/drivers/*
for script in &["build.sh", "upload.sh", "build.bat", "upload.bat"] { for script in &["build.sh", "upload.sh", "build.ps1", "upload.bat"] {
let content = fs::read_to_string(tmp.path().join(script)).unwrap(); let content = fs::read_to_string(tmp.path().join(script)).unwrap();
assert!( assert!(
content.contains("lib/drivers") || content.contains("lib\\drivers"), content.contains("lib/drivers") || content.contains("lib\\drivers") || content.contains("lib\\\\drivers"),
"{} must auto-discover lib/drivers/* for library include paths", "{} must auto-discover lib/drivers/* for library include paths",
script script
); );