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) [] -- 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); } } }