chore: move build output under build/ and scripts into scripts/

Layout:
- scripts/ holds the four build scripts plus the new clean.sh
- cmake/ holds mingw64-toolchain.cmake
- the scripts default to build/linux, build/macos, build/windows, build/harness
- docs/ holds the audit documents, with the round 1 task list archived

Cleanup:
- scripts/clean.sh removes generated output, with --dry-run, --staging,
  --legacy and --all, and refuses tracked, protected or outside paths
- the Windows VST3 manifest now comes from cmake/moduleinfo.json.in, so its
  version, vendor and class IDs follow cmake/Plugin.cmake instead of a
  hand-written heredoc that could drift
- deleted the unused Source/GUI/Slider, which no code constructed
- fixed the hidden double processBlock overload: three warning lines to zero
- unslopped the build scripts, the two .cjs helpers and the CMake comments
- .gitignore covers the upstream JUCE Projucer and DemoRunner binaries

Verified: five scripts pass bash -n; the harness passes CTest with zero
warning lines; the Linux VST3 and Standalone link; the Windows cross-build
passes its x86-64, runtime-import and entry-point checks; each configure
exposes only its own targets; and a full build leaves Source/ and
third_party/JUCE unchanged. The generated manifest is identical to the
previous hand-written one.
This commit is contained in:
biggy committed 2026-10-06 09:06:53 +02:00
1 parent e4cea300db
commit facd5c5b5e
25 files changed
+438 -313

No files matched your search

+8 -8
View File
@@ -6,7 +6,7 @@ const { execFileSync } = require('child_process');
const dir = process.env.CLAUDE_PROJECT_DIR || process.cwd(); const dir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
const BAKED = "/usr/lib/node_modules/@nanonets/graft/dist/claude"; 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) { function fromPkg(base) {
try { try {
const pkg = require.resolve('@nanonets/graft/package.json', { paths: [base] }); const pkg = require.resolve('@nanonets/graft/package.json', { paths: [base] });
@@ -14,7 +14,7 @@ function fromPkg(base) {
} catch { return null; } } 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() { function globalRoot() {
try { try {
const root = execFileSync('npm', ['root', '-g'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], shell: process.platform === 'win32' }).trim(); 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 */ } } 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) { function versionOf(distClaude) {
try { try {
return JSON.parse(fs.readFileSync(path.join(distClaude, '..', '..', 'package.json'), 'utf8')).version || null; return JSON.parse(fs.readFileSync(path.join(distClaude, '..', '..', 'package.json'), 'utf8')).version || null;
} catch { return 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) { function newer(a, b) {
if (!a) return false; if (!a) return false;
if (!b) return true; if (!b) return true;
@@ -42,7 +42,7 @@ function newer(a, b) {
return false; 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) { function best(dirs, name) {
let bestDir = null, bestVer = null; let bestDir = null, bestVer = null;
for (const d of dirs) { for (const d of dirs) {
@@ -54,14 +54,14 @@ function best(dirs, name) {
} }
function entry(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 cheap = [BAKED, fromPkg(dir), fromPkg(path.join(path.dirname(process.execPath), '..', 'lib'))];
const hit = best(cheap, name); const hit = best(cheap, name);
if (hit) return path.join(hit, name); if (hit) return path.join(hit, name);
const gr = globalRoot(); const gr = globalRoot();
const global = gr && path.join(gr, '@nanonets', 'graft', 'dist', 'claude'); const global = gr && path.join(gr, '@nanonets', 'graft', 'dist', 'claude');
if (global && fs.existsSync(path.join(global, name))) return path.join(global, name); 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 */ });
+8 -8
View File
@@ -6,7 +6,7 @@ const { execFileSync } = require('child_process');
const dir = process.env.CLAUDE_PROJECT_DIR || process.cwd(); const dir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
const BAKED = "/usr/lib/node_modules/@nanonets/graft/dist/claude"; 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) { function fromPkg(base) {
try { try {
const pkg = require.resolve('@nanonets/graft/package.json', { paths: [base] }); const pkg = require.resolve('@nanonets/graft/package.json', { paths: [base] });
@@ -14,7 +14,7 @@ function fromPkg(base) {
} catch { return null; } } 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() { function globalRoot() {
try { try {
const root = execFileSync('npm', ['root', '-g'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], shell: process.platform === 'win32' }).trim(); 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 */ } } 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) { function versionOf(distClaude) {
try { try {
return JSON.parse(fs.readFileSync(path.join(distClaude, '..', '..', 'package.json'), 'utf8')).version || null; return JSON.parse(fs.readFileSync(path.join(distClaude, '..', '..', 'package.json'), 'utf8')).version || null;
} catch { return 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) { function newer(a, b) {
if (!a) return false; if (!a) return false;
if (!b) return true; if (!b) return true;
@@ -42,7 +42,7 @@ function newer(a, b) {
return false; 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) { function best(dirs, name) {
let bestDir = null, bestVer = null; let bestDir = null, bestVer = null;
for (const d of dirs) { for (const d of dirs) {
@@ -54,14 +54,14 @@ function best(dirs, name) {
} }
function entry(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 cheap = [BAKED, fromPkg(dir), fromPkg(path.join(path.dirname(process.execPath), '..', 'lib'))];
const hit = best(cheap, name); const hit = best(cheap, name);
if (hit) return path.join(hit, name); if (hit) return path.join(hit, name);
const gr = globalRoot(); const gr = globalRoot();
const global = gr && path.join(gr, '@nanonets', 'graft', 'dist', 'claude'); const global = gr && path.join(gr, '@nanonets', 'graft', 'dist', 'claude');
if (global && fs.existsSync(path.join(global, name))) return path.join(global, name); 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 */ });
+10 -4
View File
@@ -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/
/build_*/ /build_*/
/cmake-build-*/ /cmake-build-*/
# Windows output staging # Windows distribution staging
/SerumAlt_Windows/ /SerumAlt_Windows/
# CMake generated files # CMake generated files
@@ -12,10 +13,15 @@ compile_commands.json
# IDE and OS files # IDE and OS files
.vscode/ .vscode/
.idea/ .idea
*.user *.user
.DS_Store .DS_Store
.cache/ .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/ /graft/
+12 -10
View File
@@ -62,11 +62,12 @@ safety fixes practical, without adding a generic framework.
Read README.md's Building section before changing scripts, dependencies, or CMake. Read README.md's Building section before changing scripts, dependencies, or CMake.
It is the maintained source for prerequisites, options and artifact paths. It is the maintained source for prerequisites, options and artifact paths.
- Linux production: `./build_linux.sh`. - Linux production: `./scripts/build_linux.sh`.
- macOS production, on a Mac: `./build_macos.sh`. - macOS production, on a Mac: `./scripts/build_macos.sh`.
- Windows x64 VST3, cross-compiled on Linux: `./build_windows.sh`. - Windows x64 VST3, cross-compiled on Linux: `./scripts/build_windows.sh`.
- Native QA build and execution: `./build_harness.sh --run`. - Native QA build and execution: `./scripts/build_harness.sh --run`.
- Shell syntax: `for script in build_*.sh; do bash -n "$script"; done`. - 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 Production scripts explicitly enable `SERUMALT_BUILD_PLUGIN` and disable
`SERUMALT_BUILD_TESTS`. The harness script does the reverse in a separate directory. `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 When changing CMake, verify production has no `SerumAltTest` target and the
harness-only build has no `SerumAlt`, VST3, AU, or Standalone target. 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 Keep existing build trees and local changes intact. Each script defaults to a
clean-build verification. Use a fresh Windows `OUTPUT_DIR` for distribution checks, directory under `build/`; use a new `BUILD_DIR` for clean-build verification. Use a
since staging does not remove extra files. Never edit generated CMakeCache.txt files. fresh Windows `OUTPUT_DIR` for distribution checks, since staging does not remove
The checked-in MinGW toolchain is the single source of cross-compiler configuration. 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 ## 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 directories currently hold guidance, not relocated implementations. Consult their
`AGENTS.md` files when editing the related root-level sources. Consult the matching `AGENTS.md` files when editing the related root-level sources. Consult the matching
`AGENTS.md` when editing an existing implementation subdirectory. `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 instruction to move files. Update the explicit source list and includes only when
an actual source move is part of the requested task. an actual source move is part of the requested task.
-7
View File
@@ -2,9 +2,6 @@ cmake_minimum_required(VERSION 3.22)
project(SerumAlt VERSION 1.0.0 LANGUAGES CXX) project(SerumAlt VERSION 1.0.0 LANGUAGES CXX)
# ---------------------------------------------------------------------------
# C++ standard
# ---------------------------------------------------------------------------
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) 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") message(FATAL_ERROR "Enable SERUMALT_BUILD_PLUGIN or SERUMALT_BUILD_TESTS")
endif() endif()
# ---------------------------------------------------------------------------
# JUCE
# ---------------------------------------------------------------------------
if(NOT DEFINED JUCE_ROOT) if(NOT DEFINED JUCE_ROOT)
set(JUCE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/third_party/JUCE") set(JUCE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/third_party/JUCE")
endif() endif()
@@ -54,7 +48,6 @@ set(SERUMALT_SOURCES
Source/EffectUnits/Reverb.cpp Source/EffectUnits/Reverb.cpp
Source/GUI/Knob.cpp Source/GUI/Knob.cpp
Source/GUI/KnobLookAndFeel.cpp Source/GUI/KnobLookAndFeel.cpp
Source/GUI/Slider.cpp
Source/GUI/ToggleButton.cpp Source/GUI/ToggleButton.cpp
Source/GUI/Display.cpp Source/GUI/Display.cpp
Source/GUI/WaveformDisplay.cpp Source/GUI/WaveformDisplay.cpp
+57 -37
View File
@@ -74,14 +74,15 @@ keeps its previous settings if an edit is still in progress.
## Building ## Building
Each script configures and builds its own directory. Production builds disable the The scripts live in `scripts/`. Each configures and builds its own directory under
QA harness explicitly; the harness script creates no plugin or standalone targets. `build/`. Production builds disable the QA harness explicitly; the harness script
JUCE 7.0.12 is vendored under `third_party/JUCE` and is not downloaded by the scripts. creates no plugin or standalone targets. JUCE 7.0.12 is vendored under
`third_party/JUCE` and is not downloaded by the scripts.
### Prerequisites ### Prerequisites
All scripts require Bash, CMake 3.22+, Ninja, and C/C++17 compilers. The harness The build scripts require Bash, CMake 3.22+, Ninja, and C/C++17 compilers. The
also uses CTest, included with CMake, when invoked with `--run`. 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 On Linux, install GCC or Clang, `pkg-config`, and the ALSA, FreeType, X11 and OpenGL
development libraries. For Debian/Ubuntu: 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 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 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. selects the `x86_64-w64-mingw32-*` tools and isolates target library searches.
### Production builds ### Production builds
| Host | Command | Build directory | Formats | | Host | Command | Build directory | Formats |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| Linux | `./build_linux.sh` | `build_linux/` | VST3, Standalone | | Linux | `./scripts/build_linux.sh` | `build/linux/` | VST3, Standalone |
| macOS | `./build_macos.sh` | `build_macos/` | VST3, AU, Standalone | | macOS | `./scripts/build_macos.sh` | `build/macos/` | VST3, AU, Standalone |
| Linux targeting Windows x64 | `./build_windows.sh` | `build_windows/` | VST3 only | | Linux targeting Windows x64 | `./scripts/build_windows.sh` | `build/windows/` | VST3 only |
Default Release artifacts: Default Release artifacts:
- Linux: `build_linux/SerumAlt_artefacts/Release/VST3/SerumAlt.vst3` and - Linux: `build/linux/SerumAlt_artefacts/Release/VST3/SerumAlt.vst3` and
`build_linux/SerumAlt_artefacts/Release/Standalone/SerumAlt`. `build/linux/SerumAlt_artefacts/Release/Standalone/SerumAlt`.
- macOS: `build_macos/SerumAlt_artefacts/Release/VST3/SerumAlt.vst3`, - macOS: `build/macos/SerumAlt_artefacts/Release/VST3/SerumAlt.vst3`,
`build_macos/SerumAlt_artefacts/Release/AU/SerumAlt.component`, and `build/macos/SerumAlt_artefacts/Release/AU/SerumAlt.component`, and
`build_macos/SerumAlt_artefacts/Release/Standalone/SerumAlt.app`. `build/macos/SerumAlt_artefacts/Release/Standalone/SerumAlt.app`.
- Windows: `build_windows/SerumAlt_artefacts/Release/VST3/SerumAlt.vst3`, also - Windows: `build/windows/SerumAlt_artefacts/Release/VST3/SerumAlt.vst3`, also
copied to `SerumAlt_Windows/SerumAlt.vst3`. copied to `SerumAlt_Windows/SerumAlt.vst3`.
The Windows script reuses the toolchain file without rewriting it. It injects The Windows script reuses the toolchain file without rewriting it. A cross-build
`moduleinfo.json` because the cross-compiled JUCE manifest helper cannot run on cannot run JUCE's manifest helper, because that helper is a host tool, so the
Linux. Keep this manifest's metadata in sync with `CMakeLists.txt`, script copies the `moduleinfo.json` that CMake writes from
`cmake/Plugin.cmake`, and the vendored VST3 SDK. MinGW links its C++ and threading `cmake/moduleinfo.json.in`. The plugin name, version, vendor, class IDs and VST3
runtimes statically via `-static-libgcc -static-libstdc++ -static`. Before staging, SDK version come from variables in `cmake/Plugin.cmake`, so a version bump reaches
the script verifies the linked module is x86-64, imports none of those MinGW the manifest on its own. MinGW links its C++ and threading runtimes statically via
runtimes, and exports the VST3 entry points, so a module that a stock Windows host `-static-libgcc -static-libstdc++ -static`. Before staging, the script verifies the
could not load fails the build instead of being staged silently. 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: macOS builds target the host architecture by default. To request a universal binary:
```bash ```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 `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: On Linux or macOS, build the console harness without production targets:
```bash ```bash
./build_harness.sh ./scripts/build_harness.sh
``` ```
Build and run the checks: Build and run the checks:
```bash ```bash
./build_harness.sh --run ./scripts/build_harness.sh --run
``` ```
Or rerun an already-built Release harness: Or rerun an already-built Release harness:
```bash ```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 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 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 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: For example:
```bash ```bash
BUILD_TYPE=Debug BUILD_DIR=build_harness_debug ./build_harness.sh --run BUILD_TYPE=Debug BUILD_DIR=build/harness_debug ./scripts/build_harness.sh --run
CMAKE_BUILD_PARALLEL_LEVEL=4 ./build_linux.sh CMAKE_BUILD_PARALLEL_LEVEL=4 ./scripts/build_linux.sh
``` ```
`CMakeLists.txt` owns the shared source list and target configuration. `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: `SERUMALT_BUILD_TESTS` defaults to `OFF`. A harness-only configure is:
```bash ```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 -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 An existing CMake cache retains its old option values; changing the defaults does
@@ -229,12 +232,11 @@ when configuring directly with CMake.
### Fresh builds ### Fresh builds
The scripts reuse their build directories without deleting files, resetting Git, The scripts reuse their build directories without deleting files, resetting Git,
or installing packages. Generated `build/`, `build_*/`, and `SerumAlt_Windows/` or installing packages. `build/` and `SerumAlt_Windows/` are git-ignored. Leave
directories are git-ignored. Leave existing builds intact and select an unused existing builds intact and select an unused build directory for a fresh configure:
build directory for a fresh configure:
```bash ```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 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: paths together:
```bash ```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 ## License
JUCE is licensed separately (see `third_party/JUCE/LICENSE.md`). This project is JUCE is licensed separately (see `third_party/JUCE/LICENSE.md`). This project is
+1 -1
View File
@@ -4,7 +4,7 @@
- Related editor implementation: `../PluginEditor.h`, `../PluginEditor.cpp` - Related editor implementation: `../PluginEditor.h`, `../PluginEditor.cpp`
- Knob.h, Knob.cpp - Knob.h, Knob.cpp
- Slider.h, Slider.cpp - KnobLookAndFeel.h, KnobLookAndFeel.cpp
- ToggleButton.h, ToggleButton.cpp - ToggleButton.h, ToggleButton.cpp
- Display.h, Display.cpp - Display.h, Display.cpp
- Panel.h, Panel.cpp - Panel.h, Panel.cpp
-13
View File
@@ -1,13 +0,0 @@
#include "Slider.h"
namespace serum
{
Slider::Slider (const juce::String& name, std::function<juce::String (float)> fmt)
: Knob (name, std::move (fmt))
{
setSliderStyle (juce::Slider::LinearHorizontal);
setTextBoxStyle (juce::Slider::NoTextBox, false, 0, 0);
}
} // namespace serum
-18
View File
@@ -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<juce::String (float)> formatter = {});
};
} // namespace serum
-1
View File
@@ -3,7 +3,6 @@
#include <JuceHeader.h> #include <JuceHeader.h>
#include "PluginProcessor.h" #include "PluginProcessor.h"
#include "GUI/Knob.h" #include "GUI/Knob.h"
#include "GUI/Slider.h"
#include "GUI/ToggleButton.h" #include "GUI/ToggleButton.h"
#include "GUI/Display.h" #include "GUI/Display.h"
#include "GUI/Panel.h" #include "GUI/Panel.h"
+2
View File
@@ -22,6 +22,8 @@ public:
void prepareToPlay (double sampleRate, int samplesPerBlock) override; void prepareToPlay (double sampleRate, int samplesPerBlock) override;
void releaseResources() override; void releaseResources() override;
void processBlock (juce::AudioBuffer<float>&, juce::MidiBuffer&) override; void processBlock (juce::AudioBuffer<float>&, juce::MidiBuffer&) override;
// Keeps the base class double-precision overload visible, so it is not hidden.
using juce::AudioProcessor::processBlock;
juce::AudioProcessorEditor* createEditor() override; juce::AudioProcessorEditor* createEditor() override;
bool hasEditor() const override { return true; } bool hasEditor() const override { return true; }
+1 -1
View File
@@ -16,7 +16,7 @@
## IF-THEN ## IF-THEN
- IF a check fails THEN increment the failure count and print the preset name. - 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-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. - The harness links the real processor and editor sources but creates no plugin bundles. Keep that build separation when adding checks.
-167
View File
@@ -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"
+1 -3
View File
@@ -1,6 +1,4 @@
# --------------------------------------------------------------------------- # Headless QA harness: a console app that drives the processor and checks output.
# Headless QA harness (console app that drives the processor and checks output)
# ---------------------------------------------------------------------------
juce_add_console_app(SerumAltTest) juce_add_console_app(SerumAltTest)
serumalt_configure_target(SerumAltTest) serumalt_configure_target(SerumAltTest)
target_sources(SerumAltTest PRIVATE Source/Tests/TestMain.cpp) target_sources(SerumAltTest PRIVATE Source/Tests/TestMain.cpp)
+32 -19
View File
@@ -1,14 +1,32 @@
# --------------------------------------------------------------------------- # Plugin target.
# Plugin target
# --------------------------------------------------------------------------- # Plugin metadata. It lives here once because juce_add_plugin and the cross-build
# JUCE 7.0.12 builds its VST3 manifest helper (juce_vst3_helper) with the same # VST3 manifest in cmake/moduleinfo.json.in both need the same values.
# toolchain as the plugin itself. When cross-compiling, that produces a Windows set(SERUMALT_COMPANY_NAME "SerumAlt Audio")
# .exe which cannot run on the Linux build host, so we disable the automatic set(SERUMALT_MANUFACTURER_CODE Salt)
# manifest step and inject a Windows moduleinfo.json afterwards set(SERUMALT_PLUGIN_CODE Sera)
# (see build_windows.sh). Native builds keep the automatic step. 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) set(SERUMALT_VST3_AUTO_MANIFEST TRUE)
if(CMAKE_CROSSCOMPILING) if(CMAKE_CROSSCOMPILING)
set(SERUMALT_VST3_AUTO_MANIFEST FALSE) set(SERUMALT_VST3_AUTO_MANIFEST FALSE)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/moduleinfo.json.in"
"${CMAKE_BINARY_DIR}/moduleinfo.json"
@ONLY)
endif() endif()
set(SERUMALT_FORMATS VST3) set(SERUMALT_FORMATS VST3)
@@ -21,14 +39,14 @@ endif()
juce_add_plugin(SerumAlt juce_add_plugin(SerumAlt
VERSION ${PROJECT_VERSION} VERSION ${PROJECT_VERSION}
COMPANY_NAME "SerumAlt Audio" COMPANY_NAME "${SERUMALT_COMPANY_NAME}"
IS_SYNTH TRUE IS_SYNTH TRUE
NEEDS_MIDI_INPUT TRUE NEEDS_MIDI_INPUT TRUE
NEEDS_MIDI_OUTPUT FALSE NEEDS_MIDI_OUTPUT FALSE
IS_MIDI_EFFECT FALSE IS_MIDI_EFFECT FALSE
EDITOR_WANTS_KEYBOARD_FOCUS FALSE EDITOR_WANTS_KEYBOARD_FOCUS FALSE
PLUGIN_MANUFACTURER_CODE Salt PLUGIN_MANUFACTURER_CODE ${SERUMALT_MANUFACTURER_CODE}
PLUGIN_CODE Sera PLUGIN_CODE ${SERUMALT_PLUGIN_CODE}
BUNDLE_ID com.serumalt.SerumAlt BUNDLE_ID com.serumalt.SerumAlt
FORMATS ${SERUMALT_FORMATS} FORMATS ${SERUMALT_FORMATS}
PRODUCT_NAME "SerumAlt" PRODUCT_NAME "SerumAlt"
@@ -36,14 +54,9 @@ juce_add_plugin(SerumAlt
serumalt_configure_target(SerumAlt) serumalt_configure_target(SerumAlt)
# --------------------------------------------------------------------------- # A MinGW cross-build must link libgcc, libstdc++ and winpthread statically.
# MinGW cross-build: static C++/threading runtime # 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.
# 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.
if(MINGW) if(MINGW)
target_link_options(SerumAlt_VST3 PRIVATE target_link_options(SerumAlt_VST3 PRIVATE
-static-libgcc -static-libgcc
File renamed without changes.
+57
View File
@@ -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": []
}
]
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,10 +1,11 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Build the headless QA harness. Pass --run to execute it through CTest.
set -euo pipefail set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$SCRIPT_DIR" cd "$REPO_ROOT"
BUILD_DIR="${BUILD_DIR:-$SCRIPT_DIR/build_harness}" BUILD_DIR="${BUILD_DIR:-$REPO_ROOT/build/harness}"
BUILD_TYPE="${BUILD_TYPE:-Release}" BUILD_TYPE="${BUILD_TYPE:-Release}"
export CMAKE_BUILD_PARALLEL_LEVEL="${CMAKE_BUILD_PARALLEL_LEVEL:-2}" 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 case "$(uname -s)" in
Linux) TOOLS+=(pkg-config) ;; Linux) TOOLS+=(pkg-config) ;;
Darwin) TOOLS+=(xcrun) ;; 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 esac
if [[ $# -eq 1 ]]; then if [[ $# -eq 1 ]]; then
TOOLS+=(ctest) TOOLS+=(ctest)
@@ -33,8 +34,8 @@ if [[ "$(uname -s)" == Darwin ]]; then
xcrun --sdk macosx --show-sdk-path xcrun --sdk macosx --show-sdk-path
fi fi
echo "Configuring the native QA harness only in $BUILD_DIR ($BUILD_TYPE)..." echo "Configuring the native QA harness 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" \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
-DSERUMALT_BUILD_PLUGIN=OFF \ -DSERUMALT_BUILD_PLUGIN=OFF \
-DSERUMALT_BUILD_TESTS=ON -DSERUMALT_BUILD_TESTS=ON
+6 -5
View File
@@ -1,15 +1,16 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Build the Linux production plugin: VST3 and Standalone.
set -euo pipefail set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$SCRIPT_DIR" cd "$REPO_ROOT"
BUILD_DIR="${BUILD_DIR:-$SCRIPT_DIR/build_linux}" BUILD_DIR="${BUILD_DIR:-$REPO_ROOT/build/linux}"
BUILD_TYPE="${BUILD_TYPE:-Release}" BUILD_TYPE="${BUILD_TYPE:-Release}"
export CMAKE_BUILD_PARALLEL_LEVEL="${CMAKE_BUILD_PARALLEL_LEVEL:-2}" export CMAKE_BUILD_PARALLEL_LEVEL="${CMAKE_BUILD_PARALLEL_LEVEL:-2}"
if [[ "$(uname -s)" != Linux ]]; then 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 exit 1
fi fi
@@ -21,7 +22,7 @@ for tool in cmake ninja pkg-config "${CC:-cc}" "${CXX:-c++}"; do
done done
echo "Configuring Linux production targets in $BUILD_DIR ($BUILD_TYPE)..." 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" \ -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
-DSERUMALT_BUILD_PLUGIN=ON \ -DSERUMALT_BUILD_PLUGIN=ON \
-DSERUMALT_BUILD_TESTS=OFF -DSERUMALT_BUILD_TESTS=OFF
+6 -5
View File
@@ -1,15 +1,16 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Build the macOS production plugin: VST3, AU and Standalone.
set -euo pipefail set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$SCRIPT_DIR" cd "$REPO_ROOT"
BUILD_DIR="${BUILD_DIR:-$SCRIPT_DIR/build_macos}" BUILD_DIR="${BUILD_DIR:-$REPO_ROOT/build/macos}"
BUILD_TYPE="${BUILD_TYPE:-Release}" BUILD_TYPE="${BUILD_TYPE:-Release}"
export CMAKE_BUILD_PARALLEL_LEVEL="${CMAKE_BUILD_PARALLEL_LEVEL:-2}" export CMAKE_BUILD_PARALLEL_LEVEL="${CMAKE_BUILD_PARALLEL_LEVEL:-2}"
if [[ "$(uname -s)" != Darwin ]]; then 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 exit 1
fi fi
@@ -30,7 +31,7 @@ if [[ -n "${CMAKE_OSX_DEPLOYMENT_TARGET:-}" ]]; then
fi fi
echo "Configuring macOS production targets in $BUILD_DIR ($BUILD_TYPE)..." 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[@]}" \ "${OSX_OPTIONS[@]}" \
-DSERUMALT_BUILD_PLUGIN=ON \ -DSERUMALT_BUILD_PLUGIN=ON \
-DSERUMALT_BUILD_TESTS=OFF -DSERUMALT_BUILD_TESTS=OFF
+93
View File
@@ -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"
+137
View File
@@ -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 ]