diff --git a/.claude/helpers/graft-hooks.cjs b/.claude/helpers/graft-hooks.cjs index aa307ff..6a8a12f 100755 --- a/.claude/helpers/graft-hooks.cjs +++ b/.claude/helpers/graft-hooks.cjs @@ -6,7 +6,7 @@ const { execFileSync } = require('child_process'); const dir = process.env.CLAUDE_PROJECT_DIR || process.cwd(); const BAKED = "/usr/lib/node_modules/@nanonets/graft/dist/claude"; -// The dist/claude dir of @nanonets/graft resolved from a base whose node_modules is searched. +// dist/claude inside @nanonets/graft, resolved from a directory npm searches. function fromPkg(base) { try { const pkg = require.resolve('@nanonets/graft/package.json', { paths: [base] }); @@ -14,7 +14,7 @@ function fromPkg(base) { } catch { return null; } } -// The global node_modules dir per npm (handles Homebrew/Windows/volta). Queried on demand. +// The global node_modules directory, as npm reports it. Queried only when needed. function globalRoot() { try { const root = execFileSync('npm', ['root', '-g'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], shell: process.platform === 'win32' }).trim(); @@ -22,14 +22,14 @@ function globalRoot() { } catch { return null; /* npm unavailable */ } } -// The version of the package a dist/claude dir belongs to, or null if unreadable. +// The version of the package that owns a dist/claude directory, or null. function versionOf(distClaude) { try { return JSON.parse(fs.readFileSync(path.join(distClaude, '..', '..', 'package.json'), 'utf8')).version || null; } catch { return null; } } -// Numeric-dotted compare of the release part; an unreadable version loses to any known one. +// Compares dotted version numbers. An unreadable version counts as older. function newer(a, b) { if (!a) return false; if (!b) return true; @@ -42,7 +42,7 @@ function newer(a, b) { return false; } -// The highest-versioned dir in `dirs` that actually contains `name`, or null. +// The highest-versioned entry in `dirs` that contains `name`, or null. function best(dirs, name) { let bestDir = null, bestVer = null; for (const d of dirs) { @@ -54,14 +54,14 @@ function best(dirs, name) { } function entry(name) { - // Cheap candidates first, and only shell out to npm when every one of them misses. + // Check the cheap candidates first, and run npm only if none of them match. const cheap = [BAKED, fromPkg(dir), fromPkg(path.join(path.dirname(process.execPath), '..', 'lib'))]; const hit = best(cheap, name); if (hit) return path.join(hit, name); const gr = globalRoot(); const global = gr && path.join(gr, '@nanonets', 'graft', 'dist', 'claude'); if (global && fs.existsSync(path.join(global, name))) return path.join(global, name); - return path.join(dir, 'dist', 'claude', name); // last-ditch; import will no-op if absent + return path.join(dir, 'dist', 'claude', name); // fallback; the import does nothing when the file is absent } -import(pathToFileURL(entry("hooks.js")).href).then((m) => m.main(process.argv[2])).catch(() => { /* graft unavailable, no-op */ }); +import(pathToFileURL(entry("hooks.js")).href).then((m) => m.main(process.argv[2])).catch(() => { /* graft is unavailable: do nothing */ }); diff --git a/.claude/helpers/graft-statusline.cjs b/.claude/helpers/graft-statusline.cjs index ee32aea..7d41b2c 100755 --- a/.claude/helpers/graft-statusline.cjs +++ b/.claude/helpers/graft-statusline.cjs @@ -6,7 +6,7 @@ const { execFileSync } = require('child_process'); const dir = process.env.CLAUDE_PROJECT_DIR || process.cwd(); const BAKED = "/usr/lib/node_modules/@nanonets/graft/dist/claude"; -// The dist/claude dir of @nanonets/graft resolved from a base whose node_modules is searched. +// dist/claude inside @nanonets/graft, resolved from a directory npm searches. function fromPkg(base) { try { const pkg = require.resolve('@nanonets/graft/package.json', { paths: [base] }); @@ -14,7 +14,7 @@ function fromPkg(base) { } catch { return null; } } -// The global node_modules dir per npm (handles Homebrew/Windows/volta). Queried on demand. +// The global node_modules directory, as npm reports it. Queried only when needed. function globalRoot() { try { const root = execFileSync('npm', ['root', '-g'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], shell: process.platform === 'win32' }).trim(); @@ -22,14 +22,14 @@ function globalRoot() { } catch { return null; /* npm unavailable */ } } -// The version of the package a dist/claude dir belongs to, or null if unreadable. +// The version of the package that owns a dist/claude directory, or null. function versionOf(distClaude) { try { return JSON.parse(fs.readFileSync(path.join(distClaude, '..', '..', 'package.json'), 'utf8')).version || null; } catch { return null; } } -// Numeric-dotted compare of the release part; an unreadable version loses to any known one. +// Compares dotted version numbers. An unreadable version counts as older. function newer(a, b) { if (!a) return false; if (!b) return true; @@ -42,7 +42,7 @@ function newer(a, b) { return false; } -// The highest-versioned dir in `dirs` that actually contains `name`, or null. +// The highest-versioned entry in `dirs` that contains `name`, or null. function best(dirs, name) { let bestDir = null, bestVer = null; for (const d of dirs) { @@ -54,14 +54,14 @@ function best(dirs, name) { } function entry(name) { - // Cheap candidates first, and only shell out to npm when every one of them misses. + // Check the cheap candidates first, and run npm only if none of them match. const cheap = [BAKED, fromPkg(dir), fromPkg(path.join(path.dirname(process.execPath), '..', 'lib'))]; const hit = best(cheap, name); if (hit) return path.join(hit, name); const gr = globalRoot(); const global = gr && path.join(gr, '@nanonets', 'graft', 'dist', 'claude'); if (global && fs.existsSync(path.join(global, name))) return path.join(global, name); - return path.join(dir, 'dist', 'claude', name); // last-ditch; import will no-op if absent + return path.join(dir, 'dist', 'claude', name); // fallback; the import does nothing when the file is absent } -import(pathToFileURL(entry("statusline.js")).href).then((m) => m.main()).catch(() => { /* graft unavailable, no-op */ }); +import(pathToFileURL(entry("statusline.js")).href).then((m) => m.main()).catch(() => { /* graft is unavailable: do nothing */ }); diff --git a/.gitignore b/.gitignore index ef70b62..f0d9dcc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ -# CMake build directories +# Build output. /build/ holds the default trees; the other two patterns cover a +# BUILD_DIR override and the trees that earlier script versions left at the root. /build/ /build_*/ /cmake-build-*/ -# Windows output staging +# Windows distribution staging /SerumAlt_Windows/ # CMake generated files @@ -12,10 +13,15 @@ compile_commands.json # IDE and OS files .vscode/ -.idea/ +.idea *.user .DS_Store .cache/ -# graft's local graph cache — regenerable, not committed (run `graft build`). +# Upstream JUCE release binaries. They ship with the vendored copy and are not +# built here; committing them would add 22 MB of Linux executables. +/third_party/JUCE/DemoRunner +/third_party/JUCE/Projucer + +# graft's local graph cache, regenerable and not committed (run `graft build`). /graft/ diff --git a/AGENTS.md b/AGENTS.md index 320092a..6fb5892 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -62,11 +62,12 @@ safety fixes practical, without adding a generic framework. Read README.md's Building section before changing scripts, dependencies, or CMake. It is the maintained source for prerequisites, options and artifact paths. -- Linux production: `./build_linux.sh`. -- macOS production, on a Mac: `./build_macos.sh`. -- Windows x64 VST3, cross-compiled on Linux: `./build_windows.sh`. -- Native QA build and execution: `./build_harness.sh --run`. -- Shell syntax: `for script in build_*.sh; do bash -n "$script"; done`. +- Linux production: `./scripts/build_linux.sh`. +- macOS production, on a Mac: `./scripts/build_macos.sh`. +- Windows x64 VST3, cross-compiled on Linux: `./scripts/build_windows.sh`. +- Native QA build and execution: `./scripts/build_harness.sh --run`. +- Shell syntax: `for script in scripts/*.sh; do bash -n "$script"; done`. +- Build output removal: `./scripts/clean.sh --dry-run`, then the flags you need. Production scripts explicitly enable `SERUMALT_BUILD_PLUGIN` and disable `SERUMALT_BUILD_TESTS`. The harness script does the reverse in a separate directory. @@ -74,10 +75,11 @@ Building `SerumAltTest` alone does not execute it; `--run` invokes CTest. When changing CMake, verify production has no `SerumAltTest` target and the harness-only build has no `SerumAlt`, VST3, AU, or Standalone target. -Keep existing build trees and local changes intact. Use a new `BUILD_DIR` for -clean-build verification. Use a fresh Windows `OUTPUT_DIR` for distribution checks, -since staging does not remove extra files. Never edit generated CMakeCache.txt files. -The checked-in MinGW toolchain is the single source of cross-compiler configuration. +Keep existing build trees and local changes intact. Each script defaults to a +directory under `build/`; use a new `BUILD_DIR` for clean-build verification. Use a +fresh Windows `OUTPUT_DIR` for distribution checks, since staging does not remove +extra files. Never edit generated CMakeCache.txt files. The checked-in +`cmake/mingw64-toolchain.cmake` is the single source of cross-compiler configuration. ## Current source layout @@ -95,7 +97,7 @@ The lowercase `Source/engine`, `modulation`, `params`, `plugin`, and `synth` directories currently hold guidance, not relocated implementations. Consult their `AGENTS.md` files when editing the related root-level sources. Consult the matching `AGENTS.md` when editing an existing implementation subdirectory. -`AUDIT_REPORT.md` is a historical audit and proposed refactor, not a pending +`docs/AUDIT_REPORT.md` is a historical audit and proposed refactor, not a pending instruction to move files. Update the explicit source list and includes only when an actual source move is part of the requested task. diff --git a/CMakeLists.txt b/CMakeLists.txt index 700ec95..6d02306 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,6 @@ cmake_minimum_required(VERSION 3.22) project(SerumAlt VERSION 1.0.0 LANGUAGES CXX) -# --------------------------------------------------------------------------- -# C++ standard -# --------------------------------------------------------------------------- set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) @@ -16,9 +13,6 @@ if(NOT SERUMALT_BUILD_PLUGIN AND NOT SERUMALT_BUILD_TESTS) message(FATAL_ERROR "Enable SERUMALT_BUILD_PLUGIN or SERUMALT_BUILD_TESTS") endif() -# --------------------------------------------------------------------------- -# JUCE -# --------------------------------------------------------------------------- if(NOT DEFINED JUCE_ROOT) set(JUCE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/third_party/JUCE") endif() @@ -54,7 +48,6 @@ set(SERUMALT_SOURCES Source/EffectUnits/Reverb.cpp Source/GUI/Knob.cpp Source/GUI/KnobLookAndFeel.cpp - Source/GUI/Slider.cpp Source/GUI/ToggleButton.cpp Source/GUI/Display.cpp Source/GUI/WaveformDisplay.cpp diff --git a/README.md b/README.md index 5c4447d..64db60a 100644 --- a/README.md +++ b/README.md @@ -74,14 +74,15 @@ keeps its previous settings if an edit is still in progress. ## Building -Each script configures and builds its own directory. Production builds disable the -QA harness explicitly; the harness script creates no plugin or standalone targets. -JUCE 7.0.12 is vendored under `third_party/JUCE` and is not downloaded by the scripts. +The scripts live in `scripts/`. Each configures and builds its own directory under +`build/`. Production builds disable the QA harness explicitly; the harness script +creates no plugin or standalone targets. JUCE 7.0.12 is vendored under +`third_party/JUCE` and is not downloaded by the scripts. ### Prerequisites -All scripts require Bash, CMake 3.22+, Ninja, and C/C++17 compilers. The harness -also uses CTest, included with CMake, when invoked with `--run`. +The build scripts require Bash, CMake 3.22+, Ninja, and C/C++17 compilers. The +harness script also uses CTest, included with CMake, when invoked with `--run`. On Linux, install GCC or Clang, `pkg-config`, and the ALSA, FreeType, X11 and OpenGL development libraries. For Debian/Ubuntu: @@ -120,40 +121,42 @@ sudo apt install gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 binutils-mingw-w64-x8 On Arch/CachyOS, use `sudo pacman -S --needed mingw-w64-gcc` instead. Keep the native Linux tools and libraries installed: JUCE builds `juceaide` for the host -before compiling the Windows plugin. The checked-in `mingw64-toolchain.cmake` +before compiling the Windows plugin. The checked-in `cmake/mingw64-toolchain.cmake` selects the `x86_64-w64-mingw32-*` tools and isolates target library searches. ### Production builds | Host | Command | Build directory | Formats | | --- | --- | --- | --- | -| Linux | `./build_linux.sh` | `build_linux/` | VST3, Standalone | -| macOS | `./build_macos.sh` | `build_macos/` | VST3, AU, Standalone | -| Linux targeting Windows x64 | `./build_windows.sh` | `build_windows/` | VST3 only | +| Linux | `./scripts/build_linux.sh` | `build/linux/` | VST3, Standalone | +| macOS | `./scripts/build_macos.sh` | `build/macos/` | VST3, AU, Standalone | +| Linux targeting Windows x64 | `./scripts/build_windows.sh` | `build/windows/` | VST3 only | Default Release artifacts: -- Linux: `build_linux/SerumAlt_artefacts/Release/VST3/SerumAlt.vst3` and - `build_linux/SerumAlt_artefacts/Release/Standalone/SerumAlt`. -- macOS: `build_macos/SerumAlt_artefacts/Release/VST3/SerumAlt.vst3`, - `build_macos/SerumAlt_artefacts/Release/AU/SerumAlt.component`, and - `build_macos/SerumAlt_artefacts/Release/Standalone/SerumAlt.app`. -- Windows: `build_windows/SerumAlt_artefacts/Release/VST3/SerumAlt.vst3`, also +- Linux: `build/linux/SerumAlt_artefacts/Release/VST3/SerumAlt.vst3` and + `build/linux/SerumAlt_artefacts/Release/Standalone/SerumAlt`. +- macOS: `build/macos/SerumAlt_artefacts/Release/VST3/SerumAlt.vst3`, + `build/macos/SerumAlt_artefacts/Release/AU/SerumAlt.component`, and + `build/macos/SerumAlt_artefacts/Release/Standalone/SerumAlt.app`. +- Windows: `build/windows/SerumAlt_artefacts/Release/VST3/SerumAlt.vst3`, also copied to `SerumAlt_Windows/SerumAlt.vst3`. -The Windows script reuses the toolchain file without rewriting it. It injects -`moduleinfo.json` because the cross-compiled JUCE manifest helper cannot run on -Linux. Keep this manifest's metadata in sync with `CMakeLists.txt`, -`cmake/Plugin.cmake`, and the vendored VST3 SDK. MinGW links its C++ and threading -runtimes statically via `-static-libgcc -static-libstdc++ -static`. Before staging, -the script verifies the linked module is x86-64, imports none of those MinGW -runtimes, and exports the VST3 entry points, so a module that a stock Windows host -could not load fails the build instead of being staged silently. +The Windows script reuses the toolchain file without rewriting it. A cross-build +cannot run JUCE's manifest helper, because that helper is a host tool, so the +script copies the `moduleinfo.json` that CMake writes from +`cmake/moduleinfo.json.in`. The plugin name, version, vendor, class IDs and VST3 +SDK version come from variables in `cmake/Plugin.cmake`, so a version bump reaches +the manifest on its own. MinGW links its C++ and threading runtimes statically via +`-static-libgcc -static-libstdc++ -static`. Before staging, the script verifies the +linked module is x86-64, imports none of those MinGW runtimes, and exports the VST3 +entry points, so a module that a stock Windows host could not load fails the build +instead of being staged silently. macOS builds target the host architecture by default. To request a universal binary: ```bash -CMAKE_OSX_ARCHITECTURES='arm64;x86_64' ./build_macos.sh +CMAKE_OSX_ARCHITECTURES='arm64;x86_64' ./scripts/build_macos.sh ``` `CMAKE_OSX_DEPLOYMENT_TARGET` can also be passed to that script. Architecture and @@ -166,22 +169,22 @@ and do not install plugins into a DAW's plugin directories. On Linux or macOS, build the console harness without production targets: ```bash -./build_harness.sh +./scripts/build_harness.sh ``` Build and run the checks: ```bash -./build_harness.sh --run +./scripts/build_harness.sh --run ``` Or rerun an already-built Release harness: ```bash -ctest --test-dir build_harness --build-config Release --output-on-failure --no-tests=error +ctest --test-dir build/harness --build-config Release --output-on-failure --no-tests=error ``` -The executable is `build_harness/SerumAltTest_artefacts/Release/SerumAltTest`. +The executable is `build/harness/SerumAltTest_artefacts/Release/SerumAltTest`. It renders the initial state and every factory preset, checks for non-finite samples and aggregate non-silence, and exercises the RAVE toggle. A failed check returns a nonzero exit status. This is a processor integration smoke test, not a @@ -204,8 +207,8 @@ library. It is built and run natively, not as a Windows cross-compiled executabl For example: ```bash -BUILD_TYPE=Debug BUILD_DIR=build_harness_debug ./build_harness.sh --run -CMAKE_BUILD_PARALLEL_LEVEL=4 ./build_linux.sh +BUILD_TYPE=Debug BUILD_DIR=build/harness_debug ./scripts/build_harness.sh --run +CMAKE_BUILD_PARALLEL_LEVEL=4 ./scripts/build_linux.sh ``` `CMakeLists.txt` owns the shared source list and target configuration. @@ -216,9 +219,9 @@ For direct CMake use, `SERUMALT_BUILD_PLUGIN` defaults to `ON` and `SERUMALT_BUILD_TESTS` defaults to `OFF`. A harness-only configure is: ```bash -cmake -S . -B build_harness -G Ninja -DCMAKE_BUILD_TYPE=Release \ +cmake -S . -B build/harness -G Ninja -DCMAKE_BUILD_TYPE=Release \ -DSERUMALT_BUILD_PLUGIN=OFF -DSERUMALT_BUILD_TESTS=ON -cmake --build build_harness --target SerumAltTest +cmake --build build/harness --target SerumAltTest ``` An existing CMake cache retains its old option values; changing the defaults does @@ -229,12 +232,11 @@ when configuring directly with CMake. ### Fresh builds The scripts reuse their build directories without deleting files, resetting Git, -or installing packages. Generated `build/`, `build_*/`, and `SerumAlt_Windows/` -directories are git-ignored. Leave existing builds intact and select an unused -build directory for a fresh configure: +or installing packages. `build/` and `SerumAlt_Windows/` are git-ignored. Leave +existing builds intact and select an unused build directory for a fresh configure: ```bash -BUILD_DIR=build_linux_fresh ./build_linux.sh +BUILD_DIR=build/linux_fresh ./scripts/build_linux.sh ``` Windows staging updates generated files without deleting extra files already in @@ -242,9 +244,27 @@ the destination. For a clean distribution bundle, choose unused build and output paths together: ```bash -BUILD_DIR=build_windows_fresh OUTPUT_DIR=build_windows_fresh/staged ./build_windows.sh +BUILD_DIR=build/windows_fresh OUTPUT_DIR=build/windows_fresh/staged ./scripts/build_windows.sh ``` +### Cleaning + +`scripts/clean.sh` removes generated output. A plain run removes the build trees +under `build/`; `--staging` adds `SerumAlt_Windows/`, `--legacy` adds the +`build_*` and `cmake-build-*` trees that earlier versions of the scripts left in +the repository root, and `--all` is both. `--dry-run` prints every path with its +size and removes nothing. + +```bash +./scripts/clean.sh --dry-run --all +./scripts/clean.sh --all +``` + +The script never removes a tracked file, anything under `Source/`, `third_party/` +or `.git`, or a path outside the repository. It refuses to run when `BUILD_DIR` or +`OUTPUT_DIR` names a directory outside the repository, because such a directory +belongs to another checkout. + ## License JUCE is licensed separately (see `third_party/JUCE/LICENSE.md`). This project is diff --git a/Source/GUI/AGENTS.md b/Source/GUI/AGENTS.md index ffcf603..91ff999 100644 --- a/Source/GUI/AGENTS.md +++ b/Source/GUI/AGENTS.md @@ -4,7 +4,7 @@ - Related editor implementation: `../PluginEditor.h`, `../PluginEditor.cpp` - Knob.h, Knob.cpp -- Slider.h, Slider.cpp +- KnobLookAndFeel.h, KnobLookAndFeel.cpp - ToggleButton.h, ToggleButton.cpp - Display.h, Display.cpp - Panel.h, Panel.cpp diff --git a/Source/GUI/Slider.cpp b/Source/GUI/Slider.cpp deleted file mode 100644 index 2b7892c..0000000 --- a/Source/GUI/Slider.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "Slider.h" - -namespace serum -{ - -Slider::Slider (const juce::String& name, std::function fmt) - : Knob (name, std::move (fmt)) -{ - setSliderStyle (juce::Slider::LinearHorizontal); - setTextBoxStyle (juce::Slider::NoTextBox, false, 0, 0); -} - -} // namespace serum diff --git a/Source/GUI/Slider.h b/Source/GUI/Slider.h deleted file mode 100644 index 8e0b853..0000000 --- a/Source/GUI/Slider.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "Knob.h" - -namespace serum -{ - -// =========================================================================== -// Horizontal slider (same formatting machinery as Knob). -// =========================================================================== -class Slider : public Knob -{ -public: - explicit Slider (const juce::String& name, - std::function formatter = {}); -}; - -} // namespace serum diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index c46cf24..9491d87 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -3,7 +3,6 @@ #include #include "PluginProcessor.h" #include "GUI/Knob.h" -#include "GUI/Slider.h" #include "GUI/ToggleButton.h" #include "GUI/Display.h" #include "GUI/Panel.h" diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 4dd1a84..9c870c0 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -22,6 +22,8 @@ public: void prepareToPlay (double sampleRate, int samplesPerBlock) override; void releaseResources() override; void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; + // Keeps the base class double-precision overload visible, so it is not hidden. + using juce::AudioProcessor::processBlock; juce::AudioProcessorEditor* createEditor() override; bool hasEditor() const override { return true; } diff --git a/Source/Tests/AGENTS.md b/Source/Tests/AGENTS.md index 275da74..c0d819f 100644 --- a/Source/Tests/AGENTS.md +++ b/Source/Tests/AGENTS.md @@ -16,7 +16,7 @@ ## IF-THEN - IF a check fails THEN increment the failure count and print the preset name. -- Build and run natively from the repository root with `./build_harness.sh --run`. Building the `SerumAltTest` target alone does not run checks. +- Build and run natively from the repository root with `./scripts/build_harness.sh --run`. Building the `SerumAltTest` target alone does not run checks. - The harness-only configuration sets `SERUMALT_BUILD_PLUGIN=OFF` and `SERUMALT_BUILD_TESTS=ON`. Production scripts disable the harness. - The harness links the real processor and editor sources but creates no plugin bundles. Keep that build separation when adding checks. diff --git a/build_windows.sh b/build_windows.sh deleted file mode 100755 index 18e7ddf..0000000 --- a/build_windows.sh +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/env bash -# --------------------------------------------------------------------------- -# build_windows.sh, cross-compile SerumAlt VST3 for Windows using MinGW-w64 -# --------------------------------------------------------------------------- -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" - -TOOLCHAIN_FILE="$SCRIPT_DIR/mingw64-toolchain.cmake" -BUILD_DIR="${BUILD_DIR:-$SCRIPT_DIR/build_windows}" -BUILD_TYPE="${BUILD_TYPE:-Release}" -OUTPUT_DIR="${OUTPUT_DIR:-$SCRIPT_DIR/SerumAlt_Windows}" -PLUGIN_NAME="SerumAlt" -export CMAKE_BUILD_PARALLEL_LEVEL="${CMAKE_BUILD_PARALLEL_LEVEL:-2}" - -if [[ "$(uname -s)" != Linux ]]; then - echo "ERROR: build_windows.sh cross-compiles from Linux using MinGW-w64." >&2 - exit 1 -fi - -# 1. Check for the host tools and MinGW-w64 cross-compiler. -for tool in cmake ninja pkg-config "${CC:-cc}" "${CXX:-c++}" \ - x86_64-w64-mingw32-gcc x86_64-w64-mingw32-g++ \ - x86_64-w64-mingw32-windres x86_64-w64-mingw32-ar \ - x86_64-w64-mingw32-ranlib x86_64-w64-mingw32-strip \ - x86_64-w64-mingw32-objdump; do - if ! command -v "$tool" >/dev/null 2>&1; then - echo "ERROR: $tool not found. See README.md prerequisites." >&2 - exit 1 - fi -done - -echo "Found: $(command -v x86_64-w64-mingw32-g++)" - -# 2. Use the checked-in CMake toolchain file without overwriting it. -if [[ ! -f "$TOOLCHAIN_FILE" ]]; then - echo "ERROR: Toolchain file not found: $TOOLCHAIN_FILE" >&2 - exit 1 -fi -echo "Using toolchain file: $TOOLCHAIN_FILE" - -# 3. Configure with CMake + Ninja (VST3-only cross build, no test harness). -echo "Configuring Windows VST3 in $BUILD_DIR ($BUILD_TYPE)..." -cmake -S "$SCRIPT_DIR" -B "$BUILD_DIR" -G Ninja \ - -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ - -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" \ - -DSERUMALT_BUILD_PLUGIN=ON \ - -DSERUMALT_BUILD_TESTS=OFF - -# 4. Build the VST3 target. -echo "Building Windows VST3..." -cmake --build "$BUILD_DIR" --config "$BUILD_TYPE" --target SerumAlt_VST3 - -# 5. Verify the linked module can load on a stock Windows host. -# The MinGW C++/threading runtimes are not part of this bundle and do not exist -# on a stock Windows machine, so a module that still imports one of them fails -# to load and the host then omits the plugin from its scan list. The static -# -runtime link options in cmake/Plugin.cmake are meant to prevent that; check -# it here so a stale build tree or a dropped flag fails the build loudly -# instead of shipping a bundle that appears nowhere in the host. -VST3_BUNDLE="$BUILD_DIR/${PLUGIN_NAME}_artefacts/$BUILD_TYPE/VST3/$PLUGIN_NAME.vst3" -VST3_BINARY="$VST3_BUNDLE/Contents/x86_64-win/$PLUGIN_NAME.vst3" -if [[ ! -f "$VST3_BINARY" ]]; then - echo "ERROR: cross-build produced no module at $VST3_BINARY" >&2 - exit 1 -fi -if ! x86_64-w64-mingw32-objdump -f "$VST3_BINARY" | grep -q 'i386:x86-64'; then - echo "ERROR: $VST3_BINARY is not an x86-64 module" >&2 - exit 1 -fi -VST3_DUMP="$(x86_64-w64-mingw32-objdump -p "$VST3_BINARY")" -for runtime in libgcc_s_seh-1.dll libstdc++-6.dll libwinpthread-1.dll; do - if grep -qF "DLL Name: $runtime" <<<"$VST3_DUMP"; then - echo "ERROR: $VST3_BINARY still imports $runtime." >&2 - echo " A stock Windows host cannot resolve it and will not list the" >&2 - echo " plugin. Restore the static-runtime link options in" >&2 - echo " cmake/Plugin.cmake and rebuild with a fresh BUILD_DIR." >&2 - exit 1 - fi -done -# JUCE implements only these three module entry points on Windows; bundleEntry and -# bundleExit are macOS-only, and the VST3 SDK's Windows loader requires only -# GetPluginFactory, treating InitDll/ExitDll as optional. -for entry in GetPluginFactory InitDll ExitDll; do - if ! grep -qE "[[:space:]]${entry}\$" <<<"$VST3_DUMP"; then - echo "ERROR: $VST3_BINARY does not export $entry" >&2 - exit 1 - fi -done - -# 6. Inject the VST3 moduleinfo.json. -# The automatic manifest step is disabled when cross-compiling (see -# cmake/Plugin.cmake), because JUCE's manifest helper is a host tool that would -# otherwise be built as a Windows executable. The moduleinfo content is -# deterministic for this Windows plugin. It depends on the plugin's -# name, manufacturer/plugin codes, and version, so we write the same metadata -# that the native Windows helper generates. Keep it in sync with the metadata -# in CMakeLists.txt and cmake/Plugin.cmake, including the vendored VST3 SDK version. -echo "Writing Windows VST3 manifest..." -mkdir -p "$VST3_BUNDLE/Contents/Resources" -cat > "$VST3_BUNDLE/Contents/Resources/moduleinfo.json" <<'EOF' -{ - "Name": "SerumAlt", - "Version": "1.0.0", - "Factory Info": { - "Vendor": "SerumAlt Audio", - "URL": "", - "E-Mail": "", - "Flags": { - "Unicode": true, - "Classes Discardable": false, - "Component Non Discardable": false - } - }, - "Classes": [ - { - "CID": "ABCDEF019182FAEB53616C7453657261", - "Category": "Audio Module Class", - "Name": "SerumAlt", - "Vendor": "SerumAlt Audio", - "Version": "1.0.0", - "SDKVersion": "VST 3.7.8", - "Sub Categories": [ - "Instrument", - "Synth" - ], - "Class Flags": 2, - "Cardinality": 2147483647, - "Snapshots": [] - }, - { - "CID": "ABCDEF011234ABCD53616C7453657261", - "Category": "Component Controller Class", - "Name": "SerumAlt", - "Vendor": "SerumAlt Audio", - "Version": "1.0.0", - "SDKVersion": "VST 3.7.8", - "Sub Categories": [ - "Instrument", - "Synth" - ], - "Class Flags": 2, - "Cardinality": 2147483647, - "Snapshots": [] - }, - { - "CID": "ABCDEF01C0DEF00D53616C7453657261", - "Category": "Plugin Compatibility Class", - "Name": "SerumAlt", - "Vendor": "SerumAlt Audio", - "Version": "1.0.0", - "SDKVersion": "VST 3.7.8", - "Class Flags": 0, - "Cardinality": 2147483647, - "Snapshots": [] - } - ] -} -EOF - -# 7. Copy the resulting .vst3 bundle to SerumAlt_Windows/ without deleting files. -echo "Staging Windows VST3 in $OUTPUT_DIR..." -cmake -E copy_directory "$VST3_BUNDLE" "$OUTPUT_DIR/$PLUGIN_NAME.vst3" - -echo -echo "Done. Windows VST3 plugin at: $OUTPUT_DIR/$PLUGIN_NAME.vst3" diff --git a/cmake/Harness.cmake b/cmake/Harness.cmake index b21ac13..639821e 100644 --- a/cmake/Harness.cmake +++ b/cmake/Harness.cmake @@ -1,6 +1,4 @@ -# --------------------------------------------------------------------------- -# Headless QA harness (console app that drives the processor and checks output) -# --------------------------------------------------------------------------- +# Headless QA harness: a console app that drives the processor and checks output. juce_add_console_app(SerumAltTest) serumalt_configure_target(SerumAltTest) target_sources(SerumAltTest PRIVATE Source/Tests/TestMain.cpp) diff --git a/cmake/Plugin.cmake b/cmake/Plugin.cmake index 45bdf01..871f526 100644 --- a/cmake/Plugin.cmake +++ b/cmake/Plugin.cmake @@ -1,14 +1,32 @@ -# --------------------------------------------------------------------------- -# Plugin target -# --------------------------------------------------------------------------- -# JUCE 7.0.12 builds its VST3 manifest helper (juce_vst3_helper) with the same -# toolchain as the plugin itself. When cross-compiling, that produces a Windows -# .exe which cannot run on the Linux build host, so we disable the automatic -# manifest step and inject a Windows moduleinfo.json afterwards -# (see build_windows.sh). Native builds keep the automatic step. +# Plugin target. + +# Plugin metadata. It lives here once because juce_add_plugin and the cross-build +# VST3 manifest in cmake/moduleinfo.json.in both need the same values. +set(SERUMALT_COMPANY_NAME "SerumAlt Audio") +set(SERUMALT_MANUFACTURER_CODE Salt) +set(SERUMALT_PLUGIN_CODE Sera) +set(SERUMALT_VST3_SDK_VERSION "VST 3.7.8") # ships with the vendored JUCE 7.0.12 + +# VST3 class IDs. Each is a fixed prefix, a per-class constant and the +# manufacturer and plugin codes as ASCII hex, built the same way JUCE builds +# them in juce_audio_plugin_client_VST3.cpp. +string(HEX "${SERUMALT_MANUFACTURER_CODE}${SERUMALT_PLUGIN_CODE}" serumaltCodeHex) +string(TOUPPER "${serumaltCodeHex}" serumaltCodeHex) +set(SERUMALT_CID_AUDIO_MODULE "ABCDEF019182FAEB${serumaltCodeHex}") +set(SERUMALT_CID_CONTROLLER "ABCDEF011234ABCD${serumaltCodeHex}") +set(SERUMALT_CID_COMPATIBILITY "ABCDEF01C0DEF00D${serumaltCodeHex}") + +# JUCE builds its VST3 manifest helper with the target toolchain, so during a +# Windows cross-build that helper is a Windows .exe and cannot run on the Linux +# host. Disable the automatic step and write the manifest from the template +# instead. Native builds keep JUCE's automatic step. set(SERUMALT_VST3_AUTO_MANIFEST TRUE) if(CMAKE_CROSSCOMPILING) set(SERUMALT_VST3_AUTO_MANIFEST FALSE) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/moduleinfo.json.in" + "${CMAKE_BINARY_DIR}/moduleinfo.json" + @ONLY) endif() set(SERUMALT_FORMATS VST3) @@ -21,14 +39,14 @@ endif() juce_add_plugin(SerumAlt VERSION ${PROJECT_VERSION} - COMPANY_NAME "SerumAlt Audio" + COMPANY_NAME "${SERUMALT_COMPANY_NAME}" IS_SYNTH TRUE NEEDS_MIDI_INPUT TRUE NEEDS_MIDI_OUTPUT FALSE IS_MIDI_EFFECT FALSE EDITOR_WANTS_KEYBOARD_FOCUS FALSE - PLUGIN_MANUFACTURER_CODE Salt - PLUGIN_CODE Sera + PLUGIN_MANUFACTURER_CODE ${SERUMALT_MANUFACTURER_CODE} + PLUGIN_CODE ${SERUMALT_PLUGIN_CODE} BUNDLE_ID com.serumalt.SerumAlt FORMATS ${SERUMALT_FORMATS} PRODUCT_NAME "SerumAlt" @@ -36,14 +54,9 @@ juce_add_plugin(SerumAlt serumalt_configure_target(SerumAlt) -# --------------------------------------------------------------------------- -# MinGW cross-build: static C++/threading runtime -# --------------------------------------------------------------------------- -# When cross-compiling for Windows with MinGW, link libgcc / libstdc++ / -# winpthread statically so the VST3 DLL is self-contained. Otherwise the DLL -# imports libgcc_s_seh-1.dll, libstdc++-6.dll and libwinpthread-1.dll, none of -# which ship with the bundle (or exist on a stock Windows host), so the host's -# scan would fail to load the plugin. Native (Linux) builds are unaffected. +# A MinGW cross-build must link libgcc, libstdc++ and winpthread statically. +# A stock Windows host has none of those DLLs, so a dynamic link produces a +# module that fails to load and disappears from the host's plugin list. if(MINGW) target_link_options(SerumAlt_VST3 PRIVATE -static-libgcc diff --git a/mingw64-toolchain.cmake b/cmake/mingw64-toolchain.cmake similarity index 100% rename from mingw64-toolchain.cmake rename to cmake/mingw64-toolchain.cmake diff --git a/cmake/moduleinfo.json.in b/cmake/moduleinfo.json.in new file mode 100644 index 0000000..925c13e --- /dev/null +++ b/cmake/moduleinfo.json.in @@ -0,0 +1,57 @@ +{ + "Name": "@PROJECT_NAME@", + "Version": "@PROJECT_VERSION@", + "Factory Info": { + "Vendor": "@SERUMALT_COMPANY_NAME@", + "URL": "", + "E-Mail": "", + "Flags": { + "Unicode": true, + "Classes Discardable": false, + "Component Non Discardable": false + } + }, + "Classes": [ + { + "CID": "@SERUMALT_CID_AUDIO_MODULE@", + "Category": "Audio Module Class", + "Name": "@PROJECT_NAME@", + "Vendor": "@SERUMALT_COMPANY_NAME@", + "Version": "@PROJECT_VERSION@", + "SDKVersion": "@SERUMALT_VST3_SDK_VERSION@", + "Sub Categories": [ + "Instrument", + "Synth" + ], + "Class Flags": 2, + "Cardinality": 2147483647, + "Snapshots": [] + }, + { + "CID": "@SERUMALT_CID_CONTROLLER@", + "Category": "Component Controller Class", + "Name": "@PROJECT_NAME@", + "Vendor": "@SERUMALT_COMPANY_NAME@", + "Version": "@PROJECT_VERSION@", + "SDKVersion": "@SERUMALT_VST3_SDK_VERSION@", + "Sub Categories": [ + "Instrument", + "Synth" + ], + "Class Flags": 2, + "Cardinality": 2147483647, + "Snapshots": [] + }, + { + "CID": "@SERUMALT_CID_COMPATIBILITY@", + "Category": "Plugin Compatibility Class", + "Name": "@PROJECT_NAME@", + "Vendor": "@SERUMALT_COMPANY_NAME@", + "Version": "@PROJECT_VERSION@", + "SDKVersion": "@SERUMALT_VST3_SDK_VERSION@", + "Class Flags": 0, + "Cardinality": 2147483647, + "Snapshots": [] + } + ] +} diff --git a/AUDIT_REPORT.md b/docs/AUDIT_REPORT.md similarity index 100% rename from AUDIT_REPORT.md rename to docs/AUDIT_REPORT.md diff --git a/UI_UX_AUDIT.md b/docs/UI_UX_AUDIT.md similarity index 100% rename from UI_UX_AUDIT.md rename to docs/UI_UX_AUDIT.md diff --git a/JANITOR_TASKS.md b/docs/archive/JANITOR_TASKS_2026-05.md similarity index 100% rename from JANITOR_TASKS.md rename to docs/archive/JANITOR_TASKS_2026-05.md diff --git a/build_harness.sh b/scripts/build_harness.sh similarity index 73% rename from build_harness.sh rename to scripts/build_harness.sh index 6a71fb4..b035c79 100755 --- a/build_harness.sh +++ b/scripts/build_harness.sh @@ -1,10 +1,11 @@ #!/usr/bin/env bash +# Build the headless QA harness. Pass --run to execute it through CTest. set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" -BUILD_DIR="${BUILD_DIR:-$SCRIPT_DIR/build_harness}" +BUILD_DIR="${BUILD_DIR:-$REPO_ROOT/build/harness}" BUILD_TYPE="${BUILD_TYPE:-Release}" export CMAKE_BUILD_PARALLEL_LEVEL="${CMAKE_BUILD_PARALLEL_LEVEL:-2}" @@ -17,7 +18,7 @@ TOOLS=(cmake ninja "${CC:-cc}" "${CXX:-c++}") case "$(uname -s)" in Linux) TOOLS+=(pkg-config) ;; Darwin) TOOLS+=(xcrun) ;; - *) echo "ERROR: build_harness.sh requires a native Linux or macOS host." >&2; exit 1 ;; + *) echo "ERROR: scripts/build_harness.sh requires a Linux or macOS host." >&2; exit 1 ;; esac if [[ $# -eq 1 ]]; then TOOLS+=(ctest) @@ -33,8 +34,8 @@ if [[ "$(uname -s)" == Darwin ]]; then xcrun --sdk macosx --show-sdk-path fi -echo "Configuring the native QA harness only in $BUILD_DIR ($BUILD_TYPE)..." -cmake -S "$SCRIPT_DIR" -B "$BUILD_DIR" -G Ninja \ +echo "Configuring the native QA harness in $BUILD_DIR ($BUILD_TYPE)..." +cmake -S "$REPO_ROOT" -B "$BUILD_DIR" -G Ninja \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DSERUMALT_BUILD_PLUGIN=OFF \ -DSERUMALT_BUILD_TESTS=ON diff --git a/build_linux.sh b/scripts/build_linux.sh similarity index 72% rename from build_linux.sh rename to scripts/build_linux.sh index 9579f50..96b8149 100755 --- a/build_linux.sh +++ b/scripts/build_linux.sh @@ -1,15 +1,16 @@ #!/usr/bin/env bash +# Build the Linux production plugin: VST3 and Standalone. set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" -BUILD_DIR="${BUILD_DIR:-$SCRIPT_DIR/build_linux}" +BUILD_DIR="${BUILD_DIR:-$REPO_ROOT/build/linux}" BUILD_TYPE="${BUILD_TYPE:-Release}" export CMAKE_BUILD_PARALLEL_LEVEL="${CMAKE_BUILD_PARALLEL_LEVEL:-2}" if [[ "$(uname -s)" != Linux ]]; then - echo "ERROR: build_linux.sh requires a Linux host." >&2 + echo "ERROR: scripts/build_linux.sh requires a Linux host." >&2 exit 1 fi @@ -21,7 +22,7 @@ for tool in cmake ninja pkg-config "${CC:-cc}" "${CXX:-c++}"; do done echo "Configuring Linux production targets in $BUILD_DIR ($BUILD_TYPE)..." -cmake -S "$SCRIPT_DIR" -B "$BUILD_DIR" -G Ninja \ +cmake -S "$REPO_ROOT" -B "$BUILD_DIR" -G Ninja \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ -DSERUMALT_BUILD_PLUGIN=ON \ -DSERUMALT_BUILD_TESTS=OFF diff --git a/build_macos.sh b/scripts/build_macos.sh similarity index 78% rename from build_macos.sh rename to scripts/build_macos.sh index 0e6d75e..8f3af6c 100755 --- a/build_macos.sh +++ b/scripts/build_macos.sh @@ -1,15 +1,16 @@ #!/usr/bin/env bash +# Build the macOS production plugin: VST3, AU and Standalone. set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" -BUILD_DIR="${BUILD_DIR:-$SCRIPT_DIR/build_macos}" +BUILD_DIR="${BUILD_DIR:-$REPO_ROOT/build/macos}" BUILD_TYPE="${BUILD_TYPE:-Release}" export CMAKE_BUILD_PARALLEL_LEVEL="${CMAKE_BUILD_PARALLEL_LEVEL:-2}" if [[ "$(uname -s)" != Darwin ]]; then - echo "ERROR: build_macos.sh requires macOS and the Apple SDK." >&2 + echo "ERROR: scripts/build_macos.sh requires macOS and the Apple SDK." >&2 exit 1 fi @@ -30,7 +31,7 @@ if [[ -n "${CMAKE_OSX_DEPLOYMENT_TARGET:-}" ]]; then fi echo "Configuring macOS production targets in $BUILD_DIR ($BUILD_TYPE)..." -cmake -S "$SCRIPT_DIR" -B "$BUILD_DIR" -G Ninja \ +cmake -S "$REPO_ROOT" -B "$BUILD_DIR" -G Ninja \ "${OSX_OPTIONS[@]}" \ -DSERUMALT_BUILD_PLUGIN=ON \ -DSERUMALT_BUILD_TESTS=OFF diff --git a/scripts/build_windows.sh b/scripts/build_windows.sh new file mode 100755 index 0000000..31a7a8c --- /dev/null +++ b/scripts/build_windows.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# Cross-compile the Windows x64 VST3 from Linux with MinGW-w64. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +TOOLCHAIN_FILE="$REPO_ROOT/cmake/mingw64-toolchain.cmake" +BUILD_DIR="${BUILD_DIR:-$REPO_ROOT/build/windows}" +BUILD_TYPE="${BUILD_TYPE:-Release}" +OUTPUT_DIR="${OUTPUT_DIR:-$REPO_ROOT/SerumAlt_Windows}" +PLUGIN_NAME="SerumAlt" +export CMAKE_BUILD_PARALLEL_LEVEL="${CMAKE_BUILD_PARALLEL_LEVEL:-2}" + +if [[ "$(uname -s)" != Linux ]]; then + echo "ERROR: scripts/build_windows.sh cross-compiles from Linux with MinGW-w64." >&2 + exit 1 +fi + +for tool in cmake ninja pkg-config "${CC:-cc}" "${CXX:-c++}" \ + x86_64-w64-mingw32-gcc x86_64-w64-mingw32-g++ \ + x86_64-w64-mingw32-windres x86_64-w64-mingw32-ar \ + x86_64-w64-mingw32-ranlib x86_64-w64-mingw32-strip \ + x86_64-w64-mingw32-objdump; do + if ! command -v "$tool" >/dev/null 2>&1; then + echo "ERROR: $tool not found. See README.md prerequisites." >&2 + exit 1 + fi +done + +if [[ ! -f "$TOOLCHAIN_FILE" ]]; then + echo "ERROR: toolchain file not found: $TOOLCHAIN_FILE" >&2 + exit 1 +fi + +echo "Configuring Windows VST3 in $BUILD_DIR ($BUILD_TYPE)..." +cmake -S "$REPO_ROOT" -B "$BUILD_DIR" -G Ninja \ + -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \ + -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" \ + -DSERUMALT_BUILD_PLUGIN=ON \ + -DSERUMALT_BUILD_TESTS=OFF + +echo "Building Windows VST3..." +cmake --build "$BUILD_DIR" --config "$BUILD_TYPE" --target SerumAlt_VST3 + +# A stock Windows host has neither the MinGW C++ runtime nor the MinGW threading +# runtime, so a module that imports one fails to load and the host drops it from +# its plugin list. cmake/Plugin.cmake links both statically; these checks fail the +# build when that link option is missing from a cached or a fresh build tree. +VST3_BUNDLE="$BUILD_DIR/${PLUGIN_NAME}_artefacts/$BUILD_TYPE/VST3/$PLUGIN_NAME.vst3" +VST3_BINARY="$VST3_BUNDLE/Contents/x86_64-win/$PLUGIN_NAME.vst3" +if [[ ! -f "$VST3_BINARY" ]]; then + echo "ERROR: cross-build produced no module at $VST3_BINARY" >&2 + exit 1 +fi +if ! x86_64-w64-mingw32-objdump -f "$VST3_BINARY" | grep -q 'i386:x86-64'; then + echo "ERROR: $VST3_BINARY is not an x86-64 module" >&2 + exit 1 +fi +VST3_DUMP="$(x86_64-w64-mingw32-objdump -p "$VST3_BINARY")" +for runtime in libgcc_s_seh-1.dll libstdc++-6.dll libwinpthread-1.dll; do + if grep -qF "DLL Name: $runtime" <<<"$VST3_DUMP"; then + echo "ERROR: $VST3_BINARY still imports $runtime." >&2 + echo " Restore the static-runtime link options in cmake/Plugin.cmake" >&2 + echo " and rebuild with a fresh BUILD_DIR." >&2 + exit 1 + fi +done +# JUCE defines only these entry points on Windows. bundleEntry and bundleExit are +# macOS-only, and the VST3 SDK loader requires GetPluginFactory while treating +# InitDll and ExitDll as optional. +for entry in GetPluginFactory InitDll ExitDll; do + if ! grep -qE "[[:space:]]${entry}\$" <<<"$VST3_DUMP"; then + echo "ERROR: $VST3_BINARY does not export $entry" >&2 + exit 1 + fi +done + +# JUCE's manifest helper is a host tool, so a cross-build disables it and CMake +# writes moduleinfo.json from cmake/moduleinfo.json.in instead. +MANIFEST="$BUILD_DIR/moduleinfo.json" +if [[ ! -f "$MANIFEST" ]]; then + echo "ERROR: CMake wrote no manifest at $MANIFEST" >&2 + exit 1 +fi +mkdir -p "$VST3_BUNDLE/Contents/Resources" +cmake -E copy "$MANIFEST" "$VST3_BUNDLE/Contents/Resources/moduleinfo.json" + +echo "Staging Windows VST3 in $OUTPUT_DIR..." +cmake -E copy_directory "$VST3_BUNDLE" "$OUTPUT_DIR/$PLUGIN_NAME.vst3" + +echo +echo "Done. Windows VST3 plugin at: $OUTPUT_DIR/$PLUGIN_NAME.vst3" diff --git a/scripts/clean.sh b/scripts/clean.sh new file mode 100755 index 0000000..df8b04c --- /dev/null +++ b/scripts/clean.sh @@ -0,0 +1,137 @@ +#!/usr/bin/env bash +# Remove generated build output from this checkout. +# +# Targets are limited to directories this project generates. Nothing tracked by +# git is removed, and Source/, third_party/, .git and the other project +# directories are refused even if they are named as a target. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +usage() { + cat <<'EOF' +Usage: scripts/clean.sh [options] + +Removes generated build output. With no options it removes the build trees +under build/. + +Options: + --staging also remove the staged Windows bundle, SerumAlt_Windows/ + --legacy also remove build trees left in the repository root by earlier + versions of the build scripts (build_*, cmake-build-*) + --all --staging and --legacy together + --dry-run print every path and its size, and remove nothing + -h, --help print this text +EOF +} + +DRY_RUN=false +STAGING=false +LEGACY=false +for arg in "$@"; do + case "$arg" in + --dry-run) DRY_RUN=true ;; + --staging) STAGING=true ;; + --legacy) LEGACY=true ;; + --all) STAGING=true; LEGACY=true ;; + -h|--help) usage; exit 0 ;; + *) echo "ERROR: unknown option: $arg" >&2; usage >&2; exit 2 ;; + esac +done + +if ! command -v git >/dev/null 2>&1; then + echo "ERROR: git is required to confirm that no tracked file is removed." >&2 + exit 2 +fi + +# A build directory outside the repository belongs to some other checkout, and +# this script has no business deciding whether it is disposable. +for var in BUILD_DIR OUTPUT_DIR; do + value="${!var:-}" + [ -n "$value" ] || continue + case "$value" in + /*) abs="$value" ;; + *) abs="$REPO_ROOT/$value" ;; + esac + abs="$(realpath -m -- "$abs" 2>/dev/null || printf '%s' "$abs")" + case "$abs" in + "$REPO_ROOT"/*) ;; + *) + echo "ERROR: $var=$value resolves outside $REPO_ROOT." >&2 + echo " Unset $var to clean this repository." >&2 + exit 2 + ;; + esac +done + +PROTECTED=(Source third_party .git scripts cmake docs graft) + +refused=0 +removed=0 + +remove_target() { + local path="$1" + [ -e "$path" ] || return 0 + + local abs rel size + abs="$(realpath -m -- "$path")" + rel="${abs#"$REPO_ROOT"/}" + + case "$abs" in + "$REPO_ROOT"/*) ;; + *) + echo "refusing, outside the repository: $abs" >&2 + refused=$((refused + 1)) + return 1 + ;; + esac + + local deny + for deny in "${PROTECTED[@]}"; do + if [ "$rel" = "$deny" ] || [ "${rel#"$deny"/}" != "$rel" ]; then + echo "refusing, protected path: $rel" >&2 + refused=$((refused + 1)) + return 1 + fi + done + + if [ -n "$(git ls-files -- "$rel")" ]; then + echo "refusing, tracked by git: $rel" >&2 + refused=$((refused + 1)) + return 1 + fi + + size="$(du -sh -- "$rel" 2>/dev/null | cut -f1)" + if [ "$DRY_RUN" = true ]; then + printf 'would remove %6s %s\n' "${size:-?}" "$rel" + else + rm -rf -- "$abs" + printf 'removed %6s %s\n' "${size:-?}" "$rel" + fi + removed=$((removed + 1)) +} + +TARGETS=("$REPO_ROOT/build") +if [ "$STAGING" = true ]; then + TARGETS+=("$REPO_ROOT/SerumAlt_Windows") +fi +if [ "$LEGACY" = true ]; then + shopt -s nullglob + for dir in "$REPO_ROOT"/build_* "$REPO_ROOT"/cmake-build-*; do + TARGETS+=("$dir") + done + shopt -u nullglob +fi + +for target in "${TARGETS[@]}"; do + remove_target "$target" || true +done + +if [ "$removed" -eq 0 ] && [ "$refused" -eq 0 ]; then + echo "Nothing to remove." +elif [ "$DRY_RUN" = true ]; then + echo "Dry run: $removed path(s) listed, nothing removed." +fi + +[ "$refused" -eq 0 ]