Skip to content

Commit 931a1c2

Browse files
committed
Update to latest pd, sanity check input, more improvements
1 parent 4595197 commit 931a1c2

4 files changed

Lines changed: 124 additions & 12 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Example:
1818
"name": "N-SPEC COMP LITE 2",
1919
"author": "Nasko",
2020
"path": "Plugins/N-SPEC COMP LITE 2.zip",
21+
"patch": "N-SPEC COMP LITE 2.pd",
2122
"formats": ["VST3", "AU", "LV2", "CLAP"],
2223
"type": "fx",
2324
"version": "1.0.0",
@@ -29,6 +30,7 @@ Example:
2930
"name": "N-TILT",
3031
"author": "Nasko",
3132
"path": "Plugins/N-TILT.zip",
33+
"patch": "N-TILT.pd",
3234
"formats": ["Standalone"],
3335
"type": "fx",
3436
"version": "1.0.0",
@@ -48,6 +50,7 @@ Example:
4850
| `name` | `string` | **Unique name** of the plugin. This is how it will appear in your DAW. <br>_Note: You cannot load two plugdata plugins with the same name._ |
4951
| `author` | `string` | Name of the plugin's creator, displayed inside the DAW. |
5052
| `path` | `string` | Path to the patch location within the repository. Can be a **folder** or a **.zip** file. |
53+
| `patch` | `string` | File name of the patch within the zip file or folder, must be a **.pd** file |
5154
| `formats` | `array` | List of plugin formats to build. Valid values: `VST3`, `AU`, `CLAP`, `LV2`, `Standalone`. |
5255
| `type` | `string` | Type of plugin: either `"fx"` for effects or `"instrument"` for instruments/synths. |
5356
---

build.py

Lines changed: 119 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import os
66
import shutil
77
import argparse
8+
import re
9+
import sys
810

911
parser = argparse.ArgumentParser(description="Build plugins with CMake")
1012
parser.add_argument(
@@ -26,6 +28,113 @@
2628

2729
args = parser.parse_args()
2830

31+
# ── Sanity-check helpers ────────────────────────────────────────────────────
32+
33+
KNOWN_FORMATS = {"VST3", "AU", "LV2", "CLAP", "Standalone"}
34+
VERSION_RE = re.compile(r"^\d+\.\d+\.\d+$")
35+
36+
errors = [] # fatal problems – abort after collecting all of them
37+
warnings = [] # non-fatal oddities
38+
39+
def error(msg: str):
40+
errors.append(f" ERROR: {msg}")
41+
42+
def warn(msg: str):
43+
warnings.append(f" WARNING: {msg}")
44+
45+
def validate_config(path: str) -> list:
46+
"""Load and validate config.json. Returns the parsed list or exits."""
47+
if not os.path.isfile(path):
48+
print(f"FATAL: config.json not found at '{os.path.abspath(path)}'")
49+
sys.exit(1)
50+
51+
try:
52+
with open(path) as f:
53+
data = json.load(f)
54+
except json.JSONDecodeError as e:
55+
print(f"FATAL: config.json is not valid JSON – {e}")
56+
sys.exit(1)
57+
58+
if not isinstance(data, list):
59+
print("FATAL: config.json must contain a JSON array of plugin objects.")
60+
sys.exit(1)
61+
62+
if len(data) == 0:
63+
warn("config.json contains no plugins – nothing to build.")
64+
65+
return data
66+
67+
def validate_plugin(plugin: dict, index: int):
68+
prefix = f"Plugin[{index}]"
69+
70+
# ── Required fields ──────────────────────────────────────────────────────
71+
name = plugin.get("name")
72+
if not name:
73+
error(f"{prefix}: missing required field 'name'.")
74+
elif not isinstance(name, str) or not name.strip():
75+
error(f"{prefix}: 'name' must be a non-empty string (got {name!r}).")
76+
77+
path = plugin.get("path")
78+
if not path:
79+
error(f"{prefix} ({name!r}): missing required field 'path'.")
80+
else:
81+
resolved = Path(path).resolve()
82+
if not resolved.exists():
83+
error(f"{prefix} ({name!r}): plugin path does not exist: '{resolved}'")
84+
elif not resolved.is_file():
85+
error(f"{prefix} ({name!r}): plugin path exists but is not a file: '{resolved}'")
86+
87+
# ── Optional but validated fields ────────────────────────────────────────
88+
formats = plugin.get("formats", [])
89+
if not isinstance(formats, list):
90+
error(f"{prefix} ({name!r}): 'formats' must be a list, got {type(formats).__name__}.")
91+
else:
92+
if len(formats) == 0:
93+
warn(f"{prefix} ({name!r}): 'formats' is empty – no build targets will be produced.")
94+
for fmt in formats:
95+
if fmt not in KNOWN_FORMATS:
96+
warn(f"{prefix} ({name!r}): unknown format '{fmt}'. "
97+
f"Known formats are: {', '.join(sorted(KNOWN_FORMATS))}.")
98+
99+
plugin_type = plugin.get("type", "")
100+
if plugin_type and plugin_type.lower() not in ("fx", "instrument", ""):
101+
warn(f"{prefix} ({name!r}): unexpected 'type' value '{plugin_type}'. "
102+
f"Expected 'fx' or 'instrument'.")
103+
104+
version = plugin.get("version", "1.0.0")
105+
if not VERSION_RE.match(str(version)):
106+
warn(f"{prefix} ({name!r}): 'version' value '{version}' does not follow "
107+
f"MAJOR.MINOR.PATCH format.")
108+
109+
for bool_field in ("enable_gem", "enable_sfizz", "enable_ffmpeg"):
110+
val = plugin.get(bool_field)
111+
if val is not None and not isinstance(val, bool):
112+
warn(f"{prefix} ({name!r}): '{bool_field}' should be a boolean, got {val!r}.")
113+
114+
# ── Run validation ───────────────────────────────────────────────────────────
115+
116+
plugins_config = validate_config("config.json")
117+
118+
for i, plugin in enumerate(plugins_config):
119+
if not isinstance(plugin, dict):
120+
error(f"Plugin[{i}]: expected an object, got {type(plugin).__name__}.")
121+
continue
122+
validate_plugin(plugin, i)
123+
124+
if warnings:
125+
print("Build warnings:")
126+
for w in warnings:
127+
print(w)
128+
print()
129+
130+
if errors:
131+
print("Build errors – cannot continue:")
132+
for e in errors:
133+
print(e)
134+
sys.exit(1)
135+
136+
# ── Continue with the rest of the build ─────────────────────────────────────
137+
29138
system = platform.system()
30139
if system == "Windows":
31140
cmake_compiler = ["-DCMAKE_C_COMPILER=cl", "-DCMAKE_CXX_COMPILER=cl"]
@@ -40,24 +149,23 @@
40149
else:
41150
cmake_generator = ["-GNinja"]
42151

43-
# Load config.json
44-
with open("config.json") as f:
45-
plugins_config = json.load(f)
46-
47152
plugdata_dir = Path("plugdata").resolve()
48-
builds_parent_dir = plugdata_dir.parent # Build folders go here
153+
builds_parent_dir = plugdata_dir.parent
49154

50155
plugins_dir = os.path.join("plugdata", "Plugins")
51156
build_output_dir = os.path.join("Build")
52157
os.makedirs(build_output_dir, exist_ok=True)
53158

54159
if not plugdata_dir.is_dir():
55-
print(f"plugdata directory not found: {plugdata_dir}")
56-
exit(1)
160+
print(f"FATAL: plugdata directory not found at '{plugdata_dir}'. "
161+
f"Make sure you're running this script from the repo root and that "
162+
f"the plugdata submodule has been initialised (git submodule update --init).")
163+
sys.exit(1)
57164

58165
for plugin in plugins_config:
59166
name = plugin["name"]
60167
zip_path = Path(plugin["path"]).resolve()
168+
patch = plugin["patch"]
61169
formats = plugin.get("formats", [])
62170
is_fx = plugin.get("type", "").lower() == "fx"
63171

@@ -77,6 +185,7 @@
77185
*cmake_compiler,
78186
f"-B{build_dir}",
79187
f"-DCUSTOM_PLUGIN_NAME={name}",
188+
f"-DCUSTOM_PLUGIN_PATCH={patch}",
80189
f"-DCUSTOM_PLUGIN_PATH={zip_path}",
81190
f"-DCUSTOM_PLUGIN_COMPANY={author}",
82191
f"-DCUSTOM_PLUGIN_VERSION={version}",
@@ -96,7 +205,6 @@
96205
print(f"Failed cmake configure for {name}")
97206
continue
98207

99-
# Build all combinations of type + format
100208
if not args.configure_only:
101209
for fmt in formats:
102210
if system != "Darwin" and fmt == "AU":
@@ -136,10 +244,10 @@
136244
elif fmt == "CLAP":
137245
extension = ".clap"
138246

139-
plugin_filename = name + extension;
247+
plugin_filename = name + extension
140248
os.makedirs(target_dir, exist_ok=True)
141-
src = os.path.join(format_path, plugin_filename);
142-
dst = os.path.join(target_dir, plugin_filename);
249+
src = os.path.join(format_path, plugin_filename)
250+
dst = os.path.join(target_dir, plugin_filename)
143251
if os.path.isdir(src):
144252
if os.path.exists(dst):
145253
shutil.rmtree(dst)

config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"name": "N-TILT",
44
"author": "Nasko",
55
"path": "Plugins/N-TILT.zip",
6+
"patch": "N-TILT.pd",
67
"formats": ["VST3", "AU", "LV2", "CLAP", "Standalone"],
78
"type": "fx",
89
"version": "1.0.0",

plugdata

Submodule plugdata updated 244 files

0 commit comments

Comments
 (0)