mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
Added FreeType to BuildTool
This commit is contained in:
Vendored
+1
-1
Submodule GlitchyEngine/vendor/freetype updated: 9168ee0f2c...311ccb4514
@@ -1,2 +1,29 @@
|
|||||||
# Glitchy Engine
|
# Glitchy Engine
|
||||||
A complete refocusation, reimagination and reimplementation of the world-renowned "Glitchy Engine"
|
A complete refocusation, reimagination and reimplementation of the world-renowned "Glitchy Engine"
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
To build the engine and its vendored dependencies, install the following tools on your developer machine. On Windows the commands below assume [Scoop](https://scoop.sh/) — substitute `winget`, `choco` or a manual install if you prefer.
|
||||||
|
|
||||||
|
| Tool | Purpose | Install (Windows) |
|
||||||
|
|-----------------------------|----------------------------------------------|---------------------------------------|
|
||||||
|
| Visual Studio 2022 | MSVC toolchain, MSBuild, vswhere | Installer — with the *Desktop development with C++* workload |
|
||||||
|
| .NET SDK 10 | Builds the `bin/BuildTool` C# orchestrator, also required for scripting | https://dotnet.microsoft.com/download |
|
||||||
|
| CMake | Builds `GlitchyEngineHelper` and some vendor libs | `scoop install cmake` |
|
||||||
|
| [Python 3](https://www.python.org/) + pip | Required by Meson and some vendor build scripts | `winget install Python.Python.3.13` (or use [uv](https://github.com/astral-sh/uv)) |
|
||||||
|
| [Meson](https://mesonbuild.com/) | Configures Freetype, Harfbuzz, Fribidi builds | `uv tool install meson` *or* `pip install --user meson` |
|
||||||
|
| [Ninja](https://ninja-build.org/) | Backend build system used by Meson | `scoop install ninja` |
|
||||||
|
| [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) | Resolves library metadata during Meson builds | `scoop install pkg-config` |
|
||||||
|
| Beef compiler (`beefbuild`) | Compiles the Beef workspace | https://www.beeflang.org/ |
|
||||||
|
|
||||||
|
The `BuildTool` (`bin/BuildTool`) orchestrates the vendor-library builds and the Beef workspace compile. To verify your environment is ready, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
./bin/BuildTool.exe build list
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see a list of modules (`Box2D`, `FreetypeStack`, `BeefWorkspace`, …) and the topologically-sorted build order. Running individual modules takes the form:
|
||||||
|
|
||||||
|
```
|
||||||
|
./bin/BuildTool.exe build --release FreetypeStack
|
||||||
|
```
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class Program
|
|||||||
|
|
||||||
static async Task<int> Main(string[] args)
|
static async Task<int> Main(string[] args)
|
||||||
{
|
{
|
||||||
List<Module> modules = [new BeefWorkspaceModule(), new GlitchyEngineHelperModule(), new ScriptCoreModule(), new SetupNethostModule(), new Box2DModule(), new AllModule()];
|
List<Module> modules = [new BeefWorkspaceModule(), new GlitchyEngineHelperModule(), new ScriptCoreModule(), new SetupNethostModule(), new Box2DModule(), new FreetypeStackModule(), new AllModule()];
|
||||||
|
|
||||||
// Find dependencies
|
// Find dependencies
|
||||||
foreach (Module module in modules)
|
foreach (Module module in modules)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class BeefWorkspaceModule : Module
|
|||||||
|
|
||||||
public override string Name => ModuleName;
|
public override string Name => ModuleName;
|
||||||
|
|
||||||
public override List<Type> DependencyTypes => [typeof(GlitchyEngineHelperModule), typeof(Box2DModule)];
|
public override List<Type> DependencyTypes => [typeof(GlitchyEngineHelperModule), typeof(Box2DModule), typeof(FreetypeStackModule)];
|
||||||
|
|
||||||
public override async Task<bool> Run(BuildInfo buildInfo)
|
public override async Task<bool> Run(BuildInfo buildInfo)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,287 @@
|
|||||||
|
using SimpleExec;
|
||||||
|
using static SimpleExec.Command;
|
||||||
|
|
||||||
|
namespace BuildTool.Modules;
|
||||||
|
|
||||||
|
class FreetypeStackModule : Module
|
||||||
|
{
|
||||||
|
public static readonly string ModuleName = "FreetypeStack";
|
||||||
|
|
||||||
|
public override string Name => ModuleName;
|
||||||
|
|
||||||
|
public override List<Type> DependencyTypes => [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Per-flavor build configuration. Each active flavor produces its own set of static
|
||||||
|
/// libs under <c>dist/win64/{DistSubdir}</c>, so a Beef Debug engine links against the
|
||||||
|
/// /MTd set and a Beef Release engine against the /MT set — matching Box2D's convention
|
||||||
|
/// and avoiding the <c>_ITERATOR_DEBUG_LEVEL</c> / <c>RuntimeLibrary</c> link errors that
|
||||||
|
/// a shared output directory would cause.
|
||||||
|
/// </summary>
|
||||||
|
private record Flavor(string Name, string Buildtype, string Vscrt, string DistSubdir);
|
||||||
|
|
||||||
|
private static readonly Flavor DebugFlavor = new("debug", "debug", "mtd", "debug");
|
||||||
|
private static readonly Flavor ReleaseFlavor = new("release", "release", "mt", "release");
|
||||||
|
|
||||||
|
public override async Task<bool> Run(BuildInfo buildInfo)
|
||||||
|
{
|
||||||
|
if (!buildInfo.BuildDebug && !buildInfo.BuildRelease)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
await EnsureToolsAvailableAsync();
|
||||||
|
|
||||||
|
Dictionary<string, string> vsEnv = await CaptureVsDevEnvAsync();
|
||||||
|
|
||||||
|
string workspaceRoot = buildInfo.WorkingDirectory.WorkspaceRoot;
|
||||||
|
string vendorRoot = Path.Combine(workspaceRoot, "GlitchyEngine", "vendor", "freetype");
|
||||||
|
string distRoot = Path.Combine(vendorRoot, "dist", "win64");
|
||||||
|
string hbSrcDir = Path.Combine(vendorRoot, "harfbuzz");
|
||||||
|
string fribidiSrcDir = Path.Combine(vendorRoot, "fribidi");
|
||||||
|
|
||||||
|
// Build directories live outside the submodules so Meson output never dirties them.
|
||||||
|
string buildRoot = Path.Combine(workspaceRoot, "build", "freetype-stack");
|
||||||
|
Directory.CreateDirectory(buildRoot);
|
||||||
|
|
||||||
|
if (buildInfo.BuildDebug)
|
||||||
|
await BuildFlavorAsync(DebugFlavor, hbSrcDir, fribidiSrcDir, buildRoot, distRoot, vsEnv, buildInfo.ForceRebuild);
|
||||||
|
|
||||||
|
if (buildInfo.BuildRelease)
|
||||||
|
await BuildFlavorAsync(ReleaseFlavor, hbSrcDir, fribidiSrcDir, buildRoot, distRoot, vsEnv, buildInfo.ForceRebuild);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task BuildFlavorAsync(
|
||||||
|
Flavor flavor,
|
||||||
|
string hbSrcDir, string fribidiSrcDir,
|
||||||
|
string buildRoot, string distRoot,
|
||||||
|
Dictionary<string, string> vsEnv,
|
||||||
|
bool forceRebuild)
|
||||||
|
{
|
||||||
|
string hbBuildDir = Path.Combine(buildRoot, $"harfbuzz-{flavor.Name}");
|
||||||
|
string fribidiBuildDir = Path.Combine(buildRoot, $"fribidi-{flavor.Name}");
|
||||||
|
string distDir = Path.Combine(distRoot, flavor.DistSubdir);
|
||||||
|
|
||||||
|
if (forceRebuild)
|
||||||
|
{
|
||||||
|
if (Directory.Exists(hbBuildDir)) Directory.Delete(hbBuildDir, recursive: true);
|
||||||
|
if (Directory.Exists(fribidiBuildDir)) Directory.Delete(fribidiBuildDir, recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
await BuildHarfbuzzAsync(flavor, hbSrcDir, hbBuildDir, vsEnv);
|
||||||
|
await BuildFribidiAsync(flavor, fribidiSrcDir, fribidiBuildDir, vsEnv);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(distDir);
|
||||||
|
CopyArtefacts(hbBuildDir, fribidiBuildDir, distDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that meson, ninja and pkg-config are available. These are external
|
||||||
|
/// prerequisites that the user must install once — we don't try to auto-install them
|
||||||
|
/// because each has a different canonical install path and package manager on Windows.
|
||||||
|
/// </summary>
|
||||||
|
private static async Task EnsureToolsAvailableAsync()
|
||||||
|
{
|
||||||
|
async Task Check(string tool, string args, string hint)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ReadAsync(tool, args);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception(
|
||||||
|
$"Required tool '{tool}' not found on PATH. {hint}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Check("meson", "--version",
|
||||||
|
"Install with 'uv tool install meson' (if you use uv) or 'pip install --user meson'.");
|
||||||
|
await Check("ninja", "--version",
|
||||||
|
"Install with 'scoop install ninja' or 'winget install Ninja-build.Ninja'.");
|
||||||
|
await Check("pkg-config", "--version",
|
||||||
|
"Install with 'scoop install pkg-config' or 'choco install pkgconfiglite'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs vcvarsall.bat x64 and captures the resulting environment block so that Meson's
|
||||||
|
/// subprocesses find cl.exe, link.exe and the MSVC headers/libs without needing the
|
||||||
|
/// developer to open a Developer Command Prompt manually.
|
||||||
|
/// </summary>
|
||||||
|
private static async Task<Dictionary<string, string>> CaptureVsDevEnvAsync()
|
||||||
|
{
|
||||||
|
string programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
|
||||||
|
string vswherePath = Path.Combine(programFilesX86, "Microsoft Visual Studio", "Installer", "vswhere.exe");
|
||||||
|
|
||||||
|
if (!File.Exists(vswherePath))
|
||||||
|
{
|
||||||
|
throw new Exception(
|
||||||
|
$"Could not find vswhere.exe at '{vswherePath}'. Is Visual Studio 2017 or newer installed?");
|
||||||
|
}
|
||||||
|
|
||||||
|
(string stdout, _) = await ReadAsync(vswherePath,
|
||||||
|
"-latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 " +
|
||||||
|
"-find VC/Auxiliary/Build/vcvarsall.bat");
|
||||||
|
|
||||||
|
string vcvarsallPath = stdout
|
||||||
|
.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||||
|
.FirstOrDefault() ?? "";
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(vcvarsallPath) || !File.Exists(vcvarsallPath))
|
||||||
|
{
|
||||||
|
throw new Exception(
|
||||||
|
"vswhere could not locate vcvarsall.bat. Make sure the 'Desktop development with C++' workload " +
|
||||||
|
"(component 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64') is installed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quadruple-quoted cmd /c trick: outer quotes wrap the whole payload, inner quotes
|
||||||
|
// protect the vcvarsall path because it typically contains a space.
|
||||||
|
(string envDump, _) = await ReadAsync("cmd",
|
||||||
|
$"/d /c \"\"{vcvarsallPath}\" x64 >nul && set\"");
|
||||||
|
|
||||||
|
var env = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (string line in envDump.Split('\n', StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
int idx = line.IndexOf('=');
|
||||||
|
if (idx <= 0) continue;
|
||||||
|
string key = line.Substring(0, idx).Trim();
|
||||||
|
string value = line.Substring(idx + 1).TrimEnd('\r', '\n');
|
||||||
|
env[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!env.ContainsKey("INCLUDE") || !env.ContainsKey("LIB"))
|
||||||
|
{
|
||||||
|
throw new Exception(
|
||||||
|
"vcvarsall.bat did not produce the expected MSVC environment variables (INCLUDE, LIB). " +
|
||||||
|
"Something went wrong while capturing the VS Developer environment.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task BuildHarfbuzzAsync(
|
||||||
|
Flavor flavor, string srcDir, string buildDir, Dictionary<string, string> vsEnv)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[FreetypeStack] Building Harfbuzz + Freetype ({flavor.Name}, /{flavor.Vscrt.ToUpper()}) -> {buildDir}");
|
||||||
|
|
||||||
|
void ApplyEnv(IDictionary<string, string?> target)
|
||||||
|
{
|
||||||
|
foreach (var kv in vsEnv) target[kv.Key] = kv.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Directory.Exists(buildDir))
|
||||||
|
{
|
||||||
|
// Harfbuzz' meson.build pulls Freetype in as a subproject via
|
||||||
|
// harfbuzz/subprojects/freetype2.wrap, which fetches VER-2-11-0 from GitLab the
|
||||||
|
// first time (and caches it thereafter). That gives us a separately linkable
|
||||||
|
// libfreetype.a as a side-effect of the Harfbuzz build.
|
||||||
|
await RunAsync("meson",
|
||||||
|
$"setup \"{buildDir}\" " +
|
||||||
|
$"--buildtype={flavor.Buildtype} " +
|
||||||
|
"--default-library=static " +
|
||||||
|
$"-Db_vscrt={flavor.Vscrt} " +
|
||||||
|
"--wrap-mode=default " +
|
||||||
|
"-Dfreetype=enabled " +
|
||||||
|
"-Dglib=disabled " +
|
||||||
|
"-Dgobject=disabled " +
|
||||||
|
"-Dcairo=disabled " +
|
||||||
|
"-Dchafa=disabled " +
|
||||||
|
"-Dicu=disabled " +
|
||||||
|
"-Dtests=disabled " +
|
||||||
|
"-Ddocs=disabled",
|
||||||
|
workingDirectory: srcDir,
|
||||||
|
configureEnvironment: ApplyEnv);
|
||||||
|
}
|
||||||
|
|
||||||
|
await RunAsync("meson", $"compile -C \"{buildDir}\"",
|
||||||
|
workingDirectory: srcDir,
|
||||||
|
configureEnvironment: ApplyEnv);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task BuildFribidiAsync(
|
||||||
|
Flavor flavor, string srcDir, string buildDir, Dictionary<string, string> vsEnv)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[FreetypeStack] Building Fribidi ({flavor.Name}, /{flavor.Vscrt.ToUpper()}) -> {buildDir}");
|
||||||
|
|
||||||
|
void ApplyEnv(IDictionary<string, string?> target)
|
||||||
|
{
|
||||||
|
foreach (var kv in vsEnv) target[kv.Key] = kv.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Directory.Exists(buildDir))
|
||||||
|
{
|
||||||
|
// Fribidi is LGPL 2.1+, so we ship it as a DLL + import-lib to stay on the right
|
||||||
|
// side of the LGPL's dynamic-linking exception.
|
||||||
|
await RunAsync("meson",
|
||||||
|
$"setup \"{buildDir}\" " +
|
||||||
|
$"--buildtype={flavor.Buildtype} " +
|
||||||
|
"--default-library=shared " +
|
||||||
|
$"-Db_vscrt={flavor.Vscrt} " +
|
||||||
|
"-Ddeprecated=false " +
|
||||||
|
"-Dtests=false " +
|
||||||
|
"-Ddocs=false",
|
||||||
|
workingDirectory: srcDir,
|
||||||
|
configureEnvironment: ApplyEnv);
|
||||||
|
}
|
||||||
|
|
||||||
|
await RunAsync("meson", $"compile -C \"{buildDir}\"",
|
||||||
|
workingDirectory: srcDir,
|
||||||
|
configureEnvironment: ApplyEnv);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CopyArtefacts(string hbBuildDir, string fribidiBuildDir, string distDir)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[FreetypeStack] Copying artefacts to {distDir}");
|
||||||
|
|
||||||
|
// Static libs coming out of the Harfbuzz build tree. Meson names them .a on Windows
|
||||||
|
// when default_library=static; the file format is MSVC-compatible AR so renaming
|
||||||
|
// to .lib is enough for Beef's linker.
|
||||||
|
Copy(Path.Combine(hbBuildDir, "src", "libharfbuzz.a"),
|
||||||
|
Path.Combine(distDir, "harfbuzz.lib"));
|
||||||
|
Copy(Path.Combine(hbBuildDir, "subprojects", "freetype", "libfreetype.a"),
|
||||||
|
Path.Combine(distDir, "freetype.lib"));
|
||||||
|
Copy(FindVersionedSubprojectLib(hbBuildDir, "libpng", "libpng16.a"),
|
||||||
|
Path.Combine(distDir, "png16.lib"));
|
||||||
|
Copy(FindVersionedSubprojectLib(hbBuildDir, "zlib", "libz.a"),
|
||||||
|
Path.Combine(distDir, "z.lib"));
|
||||||
|
|
||||||
|
// Fribidi: shared lib + import lib.
|
||||||
|
Copy(Path.Combine(fribidiBuildDir, "lib", "fribidi.lib"),
|
||||||
|
Path.Combine(distDir, "fribidi.lib"));
|
||||||
|
Copy(Path.Combine(fribidiBuildDir, "lib", "fribidi-0.dll"),
|
||||||
|
Path.Combine(distDir, "fribidi-0.dll"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Copy(string src, string dst)
|
||||||
|
{
|
||||||
|
if (!File.Exists(src))
|
||||||
|
throw new FileNotFoundException($"Expected build artefact not found: {src}");
|
||||||
|
|
||||||
|
File.Copy(src, dst, overwrite: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subproject directories under a Meson build tree are named with an embedded version
|
||||||
|
/// (e.g. <c>libpng-1.6.37</c>) that drifts whenever the wrap files are updated upstream,
|
||||||
|
/// so we locate by prefix instead of hard-coding the version.
|
||||||
|
/// </summary>
|
||||||
|
private static string FindVersionedSubprojectLib(string buildDir, string dirPrefix, string fileName)
|
||||||
|
{
|
||||||
|
string subprojectsDir = Path.Combine(buildDir, "subprojects");
|
||||||
|
if (!Directory.Exists(subprojectsDir))
|
||||||
|
throw new DirectoryNotFoundException($"Expected Meson subprojects dir missing: {subprojectsDir}");
|
||||||
|
|
||||||
|
foreach (string dir in Directory.EnumerateDirectories(subprojectsDir))
|
||||||
|
{
|
||||||
|
string name = Path.GetFileName(dir);
|
||||||
|
if (!name.StartsWith(dirPrefix, StringComparison.OrdinalIgnoreCase)) continue;
|
||||||
|
|
||||||
|
string candidate = Path.Combine(dir, fileName);
|
||||||
|
if (File.Exists(candidate))
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FileNotFoundException(
|
||||||
|
$"Could not find {fileName} under any subprojects/{dirPrefix}* directory in {buildDir}");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user