Faust loader: platform-aware library lookup and explicit path override
Find libfaust.so on Linux, libfaust.dylib on macOS, libfaust.dll/faust.dll on Windows; join PATH with Path.PathSeparator; accept FaustLibraryPath or AQUASYNTH_FAUST_LIBRARY as an explicit library file. Platform default homes replace the unconditional C:\Program Files\Faust. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b77c9e5b66
commit
53f1f1b03e
3 changed files with 144 additions and 16 deletions
|
|
@ -83,6 +83,14 @@ returns `AquaSynthCompiledPatch` products with `AquaSynthNativeManifest`.
|
|||
`AquaSynthRenderSession` is only a render-proof convenience wrapper for tests
|
||||
and offline one-shot buffers.
|
||||
|
||||
Library lookup is platform-aware: `libfaust.so` on Linux, `libfaust.dylib` on
|
||||
macOS, `libfaust.dll` (then `faust.dll`) on Windows, always at `<home>/lib` with
|
||||
`<home>/share/faust` beside it. `AquaSynthNativeOptions.FaustLibraryPath` or
|
||||
`AQUASYNTH_FAUST_LIBRARY` names the library file directly and overrides the home
|
||||
search; `FaustHome` then supplies `share/faust` if the prefix is not the file's
|
||||
grandparent. The loader prepends `lib` and `bin` to `PATH` using the platform
|
||||
path separator.
|
||||
|
||||
## Invariants
|
||||
|
||||
- AquaSynth remains the authority for what a patch means.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ public sealed record AquaSynthNativeOptions(
|
|||
string? FaustHome = null,
|
||||
string? DspSourceDirectory = null,
|
||||
float MinRenderSeconds = 0.05f,
|
||||
float MaxRenderSeconds = 3.0f);
|
||||
float MaxRenderSeconds = 3.0f,
|
||||
string? FaustLibraryPath = null);
|
||||
|
||||
public sealed record AquaSynthCompileIdentity(
|
||||
string Id,
|
||||
|
|
@ -484,7 +485,7 @@ public sealed class AquaSynthNativeCompiler : IDisposable
|
|||
{
|
||||
options ??= new AquaSynthNativeOptions();
|
||||
compiler = null;
|
||||
if (!FaustNativeToolchain.TryLoad(options.FaustHome, out var toolchain, out error))
|
||||
if (!FaustNativeToolchain.TryLoad(options.FaustHome, options.FaustLibraryPath, out var toolchain, out error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -648,6 +649,13 @@ public sealed class AquaSynthNativeCompiler : IDisposable
|
|||
|
||||
public static class AquaSynthFaustToolchain
|
||||
{
|
||||
public const string LibraryOverrideVariable = "AQUASYNTH_FAUST_LIBRARY";
|
||||
|
||||
public static string[] LibraryFileNames =>
|
||||
OperatingSystem.IsWindows() ? ["libfaust.dll", "faust.dll"]
|
||||
: OperatingSystem.IsMacOS() ? ["libfaust.dylib"]
|
||||
: ["libfaust.so"];
|
||||
|
||||
public static string? Find(string? preferredHome = null)
|
||||
{
|
||||
var candidates = new[]
|
||||
|
|
@ -656,7 +664,8 @@ public static class AquaSynthFaustToolchain
|
|||
Environment.GetEnvironmentVariable("AQUASYNTH_FAUST_HOME"),
|
||||
Environment.GetEnvironmentVariable("AQUARIUM_FAUST_HOME"),
|
||||
Path.Combine(AppContext.BaseDirectory, "Tools", "Faust"),
|
||||
@"C:\Program Files\Faust"
|
||||
OperatingSystem.IsWindows() ? @"C:\Program Files\Faust" : "/usr/local",
|
||||
OperatingSystem.IsWindows() ? null : "/usr"
|
||||
};
|
||||
|
||||
foreach (var candidate in candidates)
|
||||
|
|
@ -667,7 +676,7 @@ public static class AquaSynthFaustToolchain
|
|||
}
|
||||
|
||||
var root = Path.GetFullPath(candidate);
|
||||
if (File.Exists(Path.Combine(root, "lib", "faust.dll")) &&
|
||||
if (FindLibrary(root) is not null &&
|
||||
Directory.Exists(Path.Combine(root, "share", "faust")))
|
||||
{
|
||||
return root;
|
||||
|
|
@ -676,6 +685,20 @@ public static class AquaSynthFaustToolchain
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string? FindLibrary(string home) =>
|
||||
LibraryFileNames
|
||||
.Select(name => Path.Combine(home, "lib", name))
|
||||
.FirstOrDefault(File.Exists);
|
||||
|
||||
/// <summary>Explicit library file: the argument wins over AQUASYNTH_FAUST_LIBRARY. Null when neither is set.</summary>
|
||||
public static string? LibraryOverride(string? explicitPath)
|
||||
{
|
||||
var path = !string.IsNullOrWhiteSpace(explicitPath)
|
||||
? explicitPath
|
||||
: Environment.GetEnvironmentVariable(LibraryOverrideVariable);
|
||||
return string.IsNullOrWhiteSpace(path) ? null : Path.GetFullPath(path);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class FaustNativeToolchain : IDisposable
|
||||
|
|
@ -723,28 +746,47 @@ internal sealed class FaustNativeToolchain : IDisposable
|
|||
|
||||
public CreateFactoryFromStringFn CreateFactoryFromString { get; }
|
||||
|
||||
public static bool TryLoad(string? preferredHome, out FaustNativeToolchain? toolchain, out string? error)
|
||||
public static bool TryLoad(string? preferredHome, string? libraryPath, out FaustNativeToolchain? toolchain, out string? error)
|
||||
{
|
||||
toolchain = null;
|
||||
error = null;
|
||||
var home = AquaSynthFaustToolchain.Find(preferredHome);
|
||||
if (home is null)
|
||||
var dllPath = AquaSynthFaustToolchain.LibraryOverride(libraryPath);
|
||||
string home;
|
||||
if (dllPath is not null)
|
||||
{
|
||||
error = "Faust toolchain not found. Expected Tools\\Faust beside the app, AQUASYNTH_FAUST_HOME, AQUARIUM_FAUST_HOME, or C:\\Program Files\\Faust.";
|
||||
if (!File.Exists(dllPath))
|
||||
{
|
||||
error = $"Faust library override not found at {dllPath}.";
|
||||
return false;
|
||||
}
|
||||
|
||||
var dllPath = Path.Combine(home, "lib", "faust.dll");
|
||||
if (!File.Exists(dllPath))
|
||||
// lib/<file> under a prefix; an explicit home still wins for share/faust.
|
||||
home = !string.IsNullOrWhiteSpace(preferredHome)
|
||||
? Path.GetFullPath(preferredHome)
|
||||
: Path.GetFullPath(Path.Combine(Path.GetDirectoryName(dllPath)!, ".."));
|
||||
if (!Directory.Exists(Path.Combine(home, "share", "faust")))
|
||||
{
|
||||
error = $"Faust DLL not found at {dllPath}.";
|
||||
error = $"Faust share directory not found at {Path.Combine(home, "share", "faust")}; set FaustHome to the prefix that holds share/faust.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var found = AquaSynthFaustToolchain.Find(preferredHome);
|
||||
if (found is null)
|
||||
{
|
||||
error = $"Faust toolchain not found. Expected {string.Join(" or ", AquaSynthFaustToolchain.LibraryFileNames)} under <home>/lib with <home>/share/faust, where home is Tools{Path.DirectorySeparatorChar}Faust beside the app, AQUASYNTH_FAUST_HOME, AQUARIUM_FAUST_HOME, or the platform default; or set {AquaSynthFaustToolchain.LibraryOverrideVariable}.";
|
||||
return false;
|
||||
}
|
||||
|
||||
home = found;
|
||||
dllPath = AquaSynthFaustToolchain.FindLibrary(home)!;
|
||||
}
|
||||
|
||||
var binPath = Path.Combine(home, "bin");
|
||||
var libPath = Path.Combine(home, "lib");
|
||||
var libPath = Path.GetDirectoryName(dllPath)!;
|
||||
var path = Environment.GetEnvironmentVariable("PATH") ?? "";
|
||||
Environment.SetEnvironmentVariable("PATH", $"{libPath};{binPath};{path}");
|
||||
Environment.SetEnvironmentVariable("PATH", string.Join(Path.PathSeparator, [libPath, binPath, path]));
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
|||
78
tests/AquaSynth.Dsl.Tests/FaustLoaderPlatformTests.cs
Normal file
78
tests/AquaSynth.Dsl.Tests/FaustLoaderPlatformTests.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
namespace AquaSynth.Dsl.Tests;
|
||||
|
||||
public sealed class FaustLoaderPlatformTests
|
||||
{
|
||||
[Fact]
|
||||
public void LibraryFileNamesFollowThePlatform()
|
||||
{
|
||||
var names = AquaSynthFaustToolchain.LibraryFileNames;
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
Assert.Contains("libfaust.dll", names);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
Assert.Equal(["libfaust.so"], names);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindLibraryLooksUnderLibForThePlatformName()
|
||||
{
|
||||
var home = Directory.CreateTempSubdirectory("faust-home").FullName;
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.Combine(home, "lib"));
|
||||
Directory.CreateDirectory(Path.Combine(home, "share", "faust"));
|
||||
Assert.Null(AquaSynthFaustToolchain.FindLibrary(home));
|
||||
Assert.Null(AquaSynthFaustToolchain.Find(home));
|
||||
|
||||
var lib = Path.Combine(home, "lib", AquaSynthFaustToolchain.LibraryFileNames[0]);
|
||||
File.WriteAllBytes(lib, []);
|
||||
Assert.Equal(lib, AquaSynthFaustToolchain.FindLibrary(home));
|
||||
Assert.Equal(Path.GetFullPath(home), AquaSynthFaustToolchain.Find(home));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(home, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExplicitLibraryPathMustExist()
|
||||
{
|
||||
var missing = Path.Combine(Path.GetTempPath(), "no-such-dir", AquaSynthFaustToolchain.LibraryFileNames[0]);
|
||||
var loaded = AquaSynthNativeCompiler.TryLoad(
|
||||
out var compiler, out var error, new AquaSynthNativeOptions(FaustLibraryPath: missing));
|
||||
Assert.False(loaded);
|
||||
Assert.Null(compiler);
|
||||
Assert.Contains("override not found", error);
|
||||
}
|
||||
|
||||
// Real load. The source uses no Faust libraries, so a prefix without share/faust/*.lib still works.
|
||||
// Set AQUASYNTH_FAUST_LIBRARY to a libfaust file; unset, this is a no-op.
|
||||
[Fact]
|
||||
public void ExplicitLibraryPathLoadsRealFaustAndCompiles()
|
||||
{
|
||||
var library = Environment.GetEnvironmentVariable(AquaSynthFaustToolchain.LibraryOverrideVariable);
|
||||
if (string.IsNullOrWhiteSpace(library))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.True(
|
||||
AquaSynthNativeCompiler.TryLoad(out var compiler, out var error, new AquaSynthNativeOptions(FaustLibraryPath: library)),
|
||||
error);
|
||||
using (compiler)
|
||||
{
|
||||
Console.WriteLine($"SMOKE library={Path.GetFullPath(library)} faustVersion={compiler!.FaustVersion} home={compiler.FaustHome}");
|
||||
Assert.False(string.IsNullOrWhiteSpace(compiler.FaustVersion));
|
||||
|
||||
var identity = new AquaSynthCompileIdentity("smoke", "smoke", "");
|
||||
using var patch = compiler.CompileSource(identity, "smoke", "process = (+(0.0625) ~ _) : sin : *(0.1);", 0.1f);
|
||||
var samples = patch.Render();
|
||||
Console.WriteLine($"SMOKE compiled outputs={patch.Manifest.OutputCount} frames={samples.Length} peak={samples.Max(Math.Abs):F4}");
|
||||
Assert.Contains(samples, s => Math.Abs(s) > 0.01f);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue