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 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 */ });
+8 -8
View File
@@ -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 */ });
+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_*/
/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/
+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.
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.
-7
View File
@@ -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
+57 -37
View File
@@ -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
+1 -1
View File
@@ -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
-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 "PluginProcessor.h"
#include "GUI/Knob.h"
#include "GUI/Slider.h"
#include "GUI/ToggleButton.h"
#include "GUI/Display.h"
#include "GUI/Panel.h"
+2
View File
@@ -22,6 +22,8 @@ public:
void prepareToPlay (double sampleRate, int samplesPerBlock) override;
void releaseResources() 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;
bool hasEditor() const override { return true; }
+1 -1
View File
@@ -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.
-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 (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)
+32 -19
View File
@@ -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
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
# 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
+6 -5
View File
@@ -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
+6 -5
View File
@@ -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
+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 ]