Files
Cpp2Beef/CxxBuilder/src/Program.bf
2026-03-11 19:50:31 +01:00

301 lines
9.1 KiB
Beef

using System;
using System.IO;
using System.Collections;
using System.Diagnostics;
namespace CxxBuilder;
static class Program
{
[CLink] static extern int32 system(char8*);
public static int Main(String[] args)
{
if (args.Count == 3 && args[0] == "---__defsyms__")
{
GenerateAliases(args);
return 0;
}
mixin PrintUsage()
{
Console.WriteLine("""
Simple and fast build tool using clang and ninja.
Usage: $(Var CxxBuilderExe) [<options>] <patterns...> -- src=<> target=<> config=<> builddir=<> output=<> [cflags=<>]
Example: $(Var CxxBuilderExe) **.c vk_*.cpp -- "src=$(ProjectDir)/MyLib/src" target=$(TargetTriple) config=$(Configuration) "builddir=$(BuildDir)" output=MyLib cflags=-Isome/dir
--cmake - builds $src/CMakeLists.txt to build, patterns and output are ignored
! --template - TODO
! --cpp-aliases - creates aliases from the mangled c++ symbols to the symbols queried by Cpp2Beef-generated c++ bindigs
$src - The source dir, all patterns are rooted here
$target - the llvm target triple passed to clang
$config - the build configuration
$builddir - contains build artifacts and ninja files
$output - the name of the output object archive $builddir/$output.lib on windows and $builddir/$output.a elsewhere
$cflags - additional flags passed to clang
""");
return 1;
}
mixin Assert(bool condition, StringView str)
{
if (!condition)
{
Console.Error.WriteLine(str);
return 1;
}
}
enum { Default, CMake, Template } mode = .Default;
enum { None, CppAliases } actions = .None;
StringView src = null, target = null, config = null, builddir = null, output = null, cflags = null;
var iter = args.GetEnumerator();
skipPatterns: do
{
for (let arg in iter)
{
if (!arg.StartsWith("--")) continue;
switch (arg)
{
case "--": break skipPatterns;
case "--cmake":
Assert!(mode == .Default, "Conflicting options");
mode = .CMake;
/*case "--cpp-aliases":
actions |= .CppAliases;*/
default:
Console.Error.WriteLine($"Invalid option {_}");
return 1;
}
}
PrintUsage!();
}
Assert!(!(mode == .CMake && actions.HasFlag(.CppAliases)), "Comflicting options --cmake and --cpp-aliases");
for (let arg in iter)
{
var parts = arg.Split('=', 2);
StringView key = parts.GetNext().Value;
StringView value;
switch (parts.GetNext())
{
case .Err: PrintUsage!();
case .Ok(out value):
}
key.Trim();
value.Trim();
switch (key)
{
case "src": Assert!(src.IsNull, "Duplicate var 'src'"); src = value;
case "target": Assert!(target.IsNull, "Duplicate var 'target'"); target = value;
case "config": Assert!(config.IsNull, "Duplicate var 'config'"); config = value;
case "builddir": Assert!(builddir.IsNull, "Duplicate var 'builddir'"); builddir = value;
case "output": Assert!(output.IsNull, "Duplicate var 'output'"); output = value;
case "cflags": Assert!(cflags.IsNull, "Duplicate var 'cflags'"); cflags = value;
default: PrintUsage!();
}
}
Assert!(!src.IsNull, "Missing var 'src'");
Assert!(!target.IsNull, "Missing var 'target'");
Assert!(!config.IsNull, "Missing var 'config'");
Assert!(!builddir.IsNull, "Missing var 'builddir'");
Assert!(!output.IsNull || (mode == .CMake && !actions.HasFlag(.CppAliases)), "Missing var 'output'");
switch (mode)
{
case .Default:
StreamWriter writer = scope .()..Create(scope $"{builddir}/build.ninja");
{
String buffer = scope .(1024);
let currentDir = Directory.GetCurrentDirectory(..scope .(128));
void WriteVarPath(StringView key, StringView value)
{
buffer.Append(key);
buffer.Append(" = ");
Path.GetAbsolutePath(value, currentDir, buffer);
buffer.Append('\n');
}
WriteVarPath("src", src);
WriteVarPath("builddir", builddir);
buffer.AppendF($"""
target = {target}
cflags = {(config == "Release") ? "-O3" : "-O2 -g"} {cflags}
""");
writer.Write(buffer);
}
writer.Write("""
\n
cc = clang
ar = llvm-ar
rule cc
command = $cc $cflags -target $target -MD -MF $out.d -c -o $out $in
deps = gcc
depfile = $out.d
description = Building $in
rule ar
command = $ar crs $out $in
description = Creating static lib $out
""");
String ar = scope .(1024);
StringView libExtension = target.Contains("windows") ? "lib" : "a";
ar.AppendF($"\nbuild $builddir/{output}.{libExtension}: ar");
int pcount = 0;
for (let arg in args)
{
if (arg == "--") break;
pcount++;
}
StringView[] patterns = scope .[pcount];
for (var pattern in ref patterns)
{
pattern = args[@pattern];
}
void HandleMatch(StringView match)
{
writer.Write($"build $builddir/{match}.o: cc $src/{match}\n");
ar.Append(" $builddir/", match, ".o");
}
let abssrc = Path.GetAbsolutePath(src, Directory.GetCurrentDirectory(..scope .(128)), ..scope .(128));
if (FileMatcher.HandleMatches(patterns, abssrc, scope => HandleMatch) case .Err(let err))
{
Console.Error.WriteLine($"Syntax Error in Pattern: {err}");
return 1;
}
writer.WriteLine(ar);
if (actions.HasFlag(.CppAliases))
{
const String exec = .Empty
#if BF_PLATFORM_WINDOWS
+ "cmd /c ";
#endif
writer.Write($"""
nm = llvm-nm
ld = {exec}ld
rule nm
command = {exec}$nm --demangle $in > $out
description = Exporting symbols from $in
rule generate_aliases
command = "{Environment.GetExecutableFilePath(..scope .(64))}" ---__defsyms__ $in $out
description = Generating aliases
rule ld_aliases
command = $ld -r -o $out @$in "$builddir/{output}.{libExtension}"
description = Creating aliases
rule ar_add
command = $ar qs $builddir/{output}.{libExtension} $in
build $builddir/_symbols__.txt: nm $builddir/{output}.{libExtension}
build $builddir/_aliases__.txt: generate_aliases $builddir/_symbols__.txt
build $builddir/_aliases__.o: ld_aliases $builddir/_aliases__.txt
build add_to_output: ar_add $builddir/_aliases__.o
""");
}
case .CMake:
#if !DEBUG
if (File.Exists(scope $"{builddir}/build.ninja"))
break;
#endif
String cmd = scope .(512);
cmd.Append("cmake -S \"", src, "\" -B \"", builddir, "\" -GNinja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ");
cmd.Append("-DCMAKE_C_FLAGS=\"-target ", target, " ", cflags, "\" -DCMAKE_CXX_FLAGS=\"-target ", target, " ", cflags, "\" -DCMAKE_BUILD_TYPE=");
if (config == "Release") cmd.Append("Release");
else cmd.Append("RelWithDebInfo");
Assert!(system(cmd) == 0, "Build file generation failed");
case .Template: //TODO
}
var ret = system(scope $"ninja -C {builddir}");
mixin CheckRetCode(StringView fmt)
{
if (ret != 0)
{
Console.Error.WriteLine(fmt, output);
return ret;
}
}
CheckRetCode!("Failed to build {}");
/*if (actions.HasFlag(.CppAliases))
{
let aliasesDir = scope $"{builddir}/cpp2beef_aliases";
Directory.CreateDirectory(aliasesDir);
{
StreamWriter writer = scope .()..Create(scope $"{aliasesDir}/aliases.c");
ret = system(scope $"llvm-nm --export-symbols \"{builddir}/{output}.{target.Contains("windows") ? "lib" : "a"}\" > {aliasesDir}/mangled.txt");
CheckRetCode!("Failed to fetch symbols from {}");
ret = system(scope $"cat {aliasesDir}/mangled.txt | llvm-undname >{aliasesDir}/demangled.txt 2>{
#if BF_PLATFORM_WINDOWS
("nul")
#else
("/dev/null")
#endif
}");
CheckRetCode!("Failed to demangle symbols from {}");
var mangled = scope StreamReader()..Open(scope $"{aliasesDir}/magled.txt").Lines;
var demangled = scope StreamReader()..Open(scope $"{aliasesDir}/demangled.txt").Lines;
for (let dline in demangled)
{
if (dline->IsEmpty) continue;
let mline = mangled.GetNext().Value;
if (mline == dline) continue;
String identifier = scope .(dline);
for (let i < identifier.Length)
if (!identifier[i].IsLetterOrDigit)
identifier[i] = '_';
writer.Write($"extern void {identifier}() __attribute__((weak, alias(\"{mline.Value}\")));\n");
}
}
File.WriteAllText(scope $"{aliasesDir}/build.ninja", scope $"""
rule cc
command = clang -x c -target {target} -c -o $out $in
description = Compiling aliases for {output}
build aliases.o: cc aliases.c
""");
ret = system(scope $"ninja -C {aliasesDir}");
CheckRetCode!("Failed to build aliases for {}");
}*/
return 0;
}
static void GenerateAliases(String[] args)
{
StreamReader reader = scope .()..Open(args[1]);
StreamWriter writer = scope .()..Create(args[2]);
String str = scope .(256);
for (var line in reader.Lines)
{
line->Trim();
if (line->IsEmpty || line->EndsWith(':')) continue;
var split = line->Split(' ', 3);
if (split.GetNext().Value == "U") continue;
int addr = int.Parse(split.Current, .Hex);
let kind = split.GetNext().Value;
if (kind != "T" || addr == 0) continue;
let sym = split.GetNext().Value;
if (!str.IsEmpty) str..Clear()..Append(' ');
str.Append("--defsym=");
for (let i < sym.Length)
str.Append(sym[i].IsLetterOrDigit ? sym[i] : '_');
str.Append('=');
addr.ToString(str);
writer.Write(str);
}
}
}