diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e42556..173f54c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,39 @@ cmake_minimum_required(VERSION 3.14) # 3.14+ required for FetchContent_MakeAvailable +# Select the MSVC runtime library via the CMAKE_MSVC_RUNTIME_LIBRARY +# abstraction (see NUBASIC_STATIC_RUNTIME below). This requires policy CMP0091 +# to be NEW, and it must be set before project(). NEW is a safe default: when +# CMAKE_MSVC_RUNTIME_LIBRARY is left unset, targets still use the dynamic +# runtime (/MD), exactly as before. +if(POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif() +set(CMAKE_POLICY_DEFAULT_CMP0091 NEW) + project(nuBASIC) include(FetchContent) include(CTest) +# Optionally static-link the MSVC C/C++ runtime (/MT) so the produced +# executables do not depend on the Visual C++ Redistributable DLLs +# (vcruntime140.dll, vcruntime140_1.dll, msvcp140*.dll, concrt140.dll). +# The result is self-contained binaries that start on a clean Windows machine +# with no redistributable installed. Off by default to preserve the classic +# dynamic-runtime packaging; enable with -DNUBASIC_STATIC_RUNTIME=ON or the +# *-static CMake presets. +option(NUBASIC_STATIC_RUNTIME + "Statically link the MSVC C/C++ runtime (/MT) for redistributable-free binaries" + OFF) +if(MSVC AND NUBASIC_STATIC_RUNTIME) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + # Bundled dependencies fetched below (googletest, ...) are linked into the + # nuBASIC executables, so they must use the same static CRT to avoid a + # runtime-library mismatch at link time. + set(gtest_force_shared_crt OFF CACHE BOOL "" FORCE) + message(STATUS "MSVC runtime: static (/MT) — redistributable-free binaries") +endif() + if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif() diff --git a/CMakePresets.json b/CMakePresets.json index a01e054..c37bcf8 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -25,6 +25,25 @@ "CMAKE_BUILD_TYPE": "Release", "NUBASIC_WITH_RAYCAST": "ON" } + }, + { + "name": "release-static", + "displayName": "Release (static MSVC runtime)", + "binaryDir": "${sourceDir}/_generated/build/release-static", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "NUBASIC_STATIC_RUNTIME": "ON" + } + }, + { + "name": "raycast-release-static", + "displayName": "RayCast Release (static MSVC runtime)", + "binaryDir": "${sourceDir}/_generated/build/raycast-release-static", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "NUBASIC_WITH_RAYCAST": "ON", + "NUBASIC_STATIC_RUNTIME": "ON" + } } ], "buildPresets": [ @@ -42,6 +61,16 @@ "name": "raycast-release", "configurePreset": "raycast-release", "configuration": "Release" + }, + { + "name": "release-static", + "configurePreset": "release-static", + "configuration": "Release" + }, + { + "name": "raycast-release-static", + "configurePreset": "raycast-release-static", + "configuration": "Release" } ] } diff --git a/docs/windows-build-from-scratch.md b/docs/windows-build-from-scratch.md new file mode 100644 index 0000000..ccfeaad --- /dev/null +++ b/docs/windows-build-from-scratch.md @@ -0,0 +1,295 @@ +# Building nuBASIC from scratch on Windows 11 + +A step-by-step, copy-paste guide to build nuBASIC on a clean Windows 11 machine +and end up with **self-contained executables that do not need any Visual C++ +Redistributable DLLs**. This is the recommended path if a pre-built installer +fails to start (for example, a splash screen that freezes — see +[Troubleshooting](#troubleshooting)). + +Everything below was verified end-to-end on Windows 11 with Visual Studio 2026. +It works the same on Visual Studio 2022 — just substitute the generator name +`Visual Studio 17 2022` wherever `Visual Studio 18 2026` appears below. + +> ### Architecture: there is no "universal" Windows binary +> Unlike macOS, Windows executables are **not** universal/fat binaries. Each +> `.exe` targets exactly **one** CPU architecture: +> - **x64** — normal Intel/AMD PCs. This is what almost everyone needs. +> - **ARM64** — Snapdragon / Windows-on-ARM machines (e.g. Surface Pro X, +> recent Copilot+ PCs). +> +> An ARM64 `.exe` will **not** run natively on an x64 PC, and by default CMake +> builds for the **machine you are building on**. So if you build on an ARM64 +> laptop and hand the result to a friend with an Intel PC, it won't start. +> **Pick the target explicitly** with the `-A` flag (step 4) — an ARM64 host +> can cross-compile x64 and vice-versa, because the Visual Studio C++ toolset +> ships both cross-compilers. When in doubt, build **x64**. + +--- + +## 1. Install the prerequisites + +| Tool | Notes | +| ---- | ----- | +| **Visual Studio 2022 or 2026 — "Desktop development with C++" workload** | This one workload provides the MSVC compiler **and** bundles CMake + Ninja, so you do **not** need a separate CMake install. Community edition is free: . | +| **Git** | . | +| Node.js 18 LTS+ *(optional)* | Only needed to build the VS Code extension. `winget install OpenJS.NodeJS.LTS` | +| VS Code *(optional)* | Only if you want the VS Code extension. | + +> You do **not** need WiX, NSIS, or vcpkg for a working interpreter + IDE. +> Those are only for building the MSI installer / enabling native `Declare` +> calls (see the optional sections). + +## 2. Open the right shell + +Use the **Developer PowerShell for VS 2026** (or 2022) from the Start menu — do +**not** use a plain PowerShell window. The developer shell puts `cmake`, +`ninja`, and the MSVC compiler on `PATH` automatically. Verify: + +```powershell +cmake --version # should print e.g. "cmake version 4.1.x-msvc1" +cl # should print the Microsoft C/C++ compiler banner +``` + +## 3. Clone the repository + +```powershell +cd $HOME\Documents +git clone https://github.com/eantcal/nubasic.git +cd nubasic +``` + +## 4. Configure — static runtime + explicit architecture + +This is the important part. `NUBASIC_STATIC_RUNTIME=ON` static-links the MSVC +C/C++ runtime (`/MT`), so the resulting `.exe` files embed the runtime and start +on a clean machine **without** `vcruntime140.dll`, `msvcp140.dll`, +`concrt140.dll`, etc. `-A x64` pins the target architecture (see the box at the +top — do not rely on the default). + +**Recommended (x64, self-contained, with the WinRaycast engine):** + +```powershell +cmake -S . -B _generated\build\release-static-x64 ` + -G "Visual Studio 18 2026" -A x64 ` + -DCMAKE_BUILD_TYPE=Release -DNUBASIC_WITH_RAYCAST=ON -DNUBASIC_STATIC_RUNTIME=ON +``` + +For an **ARM64** machine, change `-A x64` to `-A ARM64` and the build directory +name to match (e.g. `release-static-arm64`). + +There are also convenience CMake presets when you just want to target the host +architecture: + +| Preset | Raycaster | MSVC runtime | Notes | +| ------ | --------- | ------------ | ----- | +| `raycast-release-static` | on | **static (/MT)** | self-contained + WinRaycast, host arch | +| `release-static` | (default) | **static (/MT)** | self-contained, host arch | +| `raycast-release` | on | dynamic (/MD) | needs VC++ redistributable installed | +| `release` | (default) | dynamic (/MD) | needs VC++ redistributable installed | + +```powershell +# host-architecture shortcut equivalent to the recommended command above +cmake --preset raycast-release-static +``` + +> The presets build for whatever architecture you are on. To build **x64 on an +> ARM64 machine** (or vice-versa), use the explicit `-A` command above instead +> of a preset. + +The first configure downloads Scintilla, Lexilla, googletest and nlohmann/json +via CMake FetchContent, so it takes a couple of minutes. A harmless +`libffi not found` warning is expected (see [native calls](#optional-native-declare-calls-libffi--vcpkg)). + +In the configure output you should see the line +`MSVC runtime: static (/MT) — redistributable-free binaries`, which confirms the +static runtime is active. + +## 5. Build + +Use the same build directory you configured in step 4 (the examples below use +the recommended x64 directory `_generated\build\release-static-x64`). + +```powershell +# Core interpreter, GDI graphics console, and VS Code debug backend +cmake --build _generated\build\release-static-x64 --target nuBasicCLI nuBasicGDI nuBasicDebugCLI --config Release + +# Optional: the Scintilla-based desktop IDE +cmake --build _generated\build\release-static-x64 --target nuBasicIDE --config Release +``` + +The executables land here: + +| File | Path (under your build dir) | What it is | +| ---- | --- | ---------- | +| `nubasic.exe` | `cli\win\Release\` | interpreter / REPL (console) | +| `nubasicgdi.exe` | `cli\win\Release\` | interpreter with a GDI graphics window | +| `nubasicdebug.exe` | `cli\win\Release\` | debug backend for the VS Code extension | +| `NuBasicIDE.exe` | `ide\win\Release\` | desktop IDE (needs `SciLexer.dll`, copied next to it automatically) | + +## 6. Run "Hello, world" + +```powershell +cd _generated\build\release-static-x64\cli\win\Release +".\nubasic.exe" --version +``` + +Create `hello.bas` **as UTF-8 without a BOM** (this matters — see the gotcha +below) and run it: + +```powershell +# This writes the file WITHOUT a byte-order mark: +[System.IO.File]::WriteAllText("$PWD\hello.bas", "Print ""Hello, world!""`r`n", (New-Object System.Text.UTF8Encoding($false))) +".\nubasic.exe" -t hello.bas +``` + +Expected output: `Hello, world!` + +> ### ⚠️ The UTF-8 BOM gotcha +> If you create a `.bas` file with `Set-Content -Encoding utf8`, Notepad's +> default "UTF-8 with BOM", or similar, Windows prepends an invisible byte-order +> mark. nuBASIC reads it as part of the first token and you get: +> `Runtime Error #4 ... print procedure not defined`. +> **Save `.bas` files as UTF-8 *without* BOM** (Notepad: *Save as → Encoding: +> UTF-8*, not "UTF-8 with BOM"; VS Code: bottom-right encoding selector). + +## 7. Run the test suite (optional but recommended) + +```powershell +cd $HOME\Documents\nubasic +.\tests\run_tests.ps1 -Interpreter .\_generated\build\release-static-x64\cli\win\Release\nubasic.exe +``` + +A healthy run reports the language suites passing. With this build the only +"failures" you should see are the four `test_native_*` suites — they require +libffi, which is off unless you install vcpkg (see below). Fourteen POSIX-only +suites are skipped on Windows by design. + +--- + +## Optional: the VS Code extension + +The extension gives you syntax highlighting plus an F5 debugger (breakpoints, +step in/over/out, watch, call stack) backed by `nubasicdebug.exe`. + +```powershell +# Build the .vsix (requires Node.js on PATH) +cmake --build _generated\build\release-static-x64 --target VscodeExtensionBuild --config Release + +# Install it into VS Code +code --install-extension vscode-nubasic\nubasic-latest.vsix --force +``` + +Then tell the extension where your interpreter is. Open VS Code **Settings +(JSON)** and add — pointing at the `nubasic.exe` you built (the extension finds +`nubasicdebug.exe` next to it automatically): + +```json +{ + "nubasic.executablePath": "C:/Users//Documents/nubasic/_generated/build/release-static-x64/cli/win/Release/nubasic.exe" +} +``` + +Open a `.bas` file and press **F5** to debug or **Ctrl+F5** to run. + +> **Version-match matters.** The extension talks to `nubasicdebug.exe` over a +> `--machine-interface` protocol and expects it to *stop on entry*. If you point +> `executablePath` at an old installed copy while using a newer extension (or +> vice-versa), debugging can appear to hang. Always point the extension at the +> `nubasicdebug.exe` from the **same build**. Building both from this repo, as +> above, keeps them in sync. + +--- + +## Optional: native `Declare` calls (libffi + vcpkg) + +nuBASIC can call into native DLLs (`Declare Function ... Lib "kernel32.dll"`). +That needs **libffi**, which on Windows comes from **vcpkg**. Skip this unless +you need native calls — everything else works without it. + +```powershell +git clone https://github.com/microsoft/vcpkg C:\vcpkg +C:\vcpkg\bootstrap-vcpkg.bat +C:\vcpkg\vcpkg install libffi:x64-windows # use arm64-windows on ARM64 +$env:VCPKG_ROOT = "C:\vcpkg" +``` + +Re-run the configure (step 4) with `VCPKG_ROOT` set; CMake will detect libffi +and enable native invocation. Note that libffi ships as `ffi-8.dll`, which must +sit next to the executables — one more reason the static-runtime build is the +simplest to distribute when you don't need native calls. + +--- + +## Troubleshooting + +### Symptom: the app freezes on a splash screen, or complains about missing `vcruntime140.dll` / `msvcp140.dll` + +**Cause:** the binaries were built against the *dynamic* MSVC runtime (`/MD`) +and the machine has no Visual C++ Redistributable installed. On a clean Windows +box the process can't load its runtime DLLs and dies before (or at) the splash. + +**Fix — this whole guide.** Build with a `*-static` preset (step 4). The +`/MT` runtime is baked into each `.exe`, so there are no VC++ DLLs to be +missing. Confirm a finished build has no runtime-DLL imports: + +```powershell +dumpbin /dependents .\_generated\build\release-static-x64\cli\win\Release\nubasic.exe +``` + +You should **not** see `vcruntime140.dll`, `vcruntime140_1.dll`, `msvcp140.dll`, +or `concrt140.dll` in the list — only core OS DLLs like `KERNEL32.dll`, +`USER32.dll`, `GDI32.dll`, `gdiplus.dll`. + +*(Alternative without rebuilding: install the Microsoft Visual C++ 2015–2022 +Redistributable for the machine's architecture — but static-linking removes the +dependency for good.)* + +### Symptom: "This app can't run on your PC" / the exe does nothing + +The binary is the **wrong architecture** for the machine (e.g. an ARM64 build on +an Intel PC). Check what a binary actually is: + +```powershell +dumpbin /headers .\nubasic.exe | Select-String "machine" +# 8664 machine (x64) -> Intel/AMD PC +# AA64 machine (ARM64) -> Windows-on-ARM PC +``` + +Rebuild for the correct architecture with the right `-A` flag (step 4). + +### The desktop IDE (`NuBasicIDE.exe`) needs `SciLexer.dll` + +`SciLexer.dll` is nuBASIC's own Scintilla component (not a system DLL). The +build copies it next to `NuBasicIDE.exe` automatically. If you move the IDE +`.exe`, take `SciLexer.dll` with it. + +### `Runtime Error #4 ... procedure not defined` on the first line + +Your `.bas` file has a UTF-8 BOM. Re-save it as UTF-8 *without* BOM (see the +gotcha in step 6). + +### Deeper installer diagnostics + +For MSI-install failures specifically, the repo ships a diagnostic collector and +checklist — see [`windows-installer-diagnostics.md`](windows-installer-diagnostics.md) +and [`windows-runtime-dependencies.md`](windows-runtime-dependencies.md). + +--- + +## Giving a working copy to someone else (no build required) + +Because the `*-static` **x64** build has no external runtime dependencies, the +simplest way to help someone whose installer won't start is to just hand them +the built binaries — no installer, no redistributable, no admin rights: + +1. Build the **x64 static** configuration (steps 4–5). +2. Zip up the `Release` folder(s): + - `…\release-static-x64\cli\win\Release\` → `nubasic.exe`, `nubasicgdi.exe`, + `nubasicdebug.exe` + - `…\release-static-x64\ide\win\Release\` → `NuBasicIDE.exe` **and** + `SciLexer.dll` (keep these two together) +3. Send the zip. They unzip anywhere and double-click `NuBasicIDE.exe`, or run + `nubasic.exe` from a terminal. Nothing to install. + +Make sure it's the **x64** build unless you know their machine is ARM64 — see +the architecture note at the top.