From e12afb619f3845e64a4daeb8a5088b348ee3afc7 Mon Sep 17 00:00:00 2001 From: Ben Visness Date: Fri, 7 Jul 2023 11:25:49 -0500 Subject: [PATCH] Get milepost mac build working in Python --- .gitignore | 3 + scripts/build_runtime.py | 125 +++++++++++++++++++++++++++++++++++++++ scripts/orca.py | 13 ++++ scripts/utils.py | 32 ++++++++++ 4 files changed, 173 insertions(+) create mode 100644 scripts/build_runtime.py create mode 100755 scripts/orca.py create mode 100644 scripts/utils.py diff --git a/.gitignore b/.gitignore index 695d9d9..23ada4b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,7 @@ sdk/io_stubs.c sdk/orca_surface.c *bind_gen.c +.vscode/launch.json .vscode/settings.json + +__pycache__ diff --git a/scripts/build_runtime.py b/scripts/build_runtime.py new file mode 100644 index 0000000..e9b48f4 --- /dev/null +++ b/scripts/build_runtime.py @@ -0,0 +1,125 @@ +import argparse +import os +import platform +import subprocess + +from utils import pushd, removeall, shellish + + +def attach_build_runtime(subparsers): + build = subparsers.add_parser("build-runtime", help="TODO") + build.add_argument("--release", action="store_true", help="compile Orca in release mode (default is debug)") + build.set_defaults(func=shellish(build_runtime)) + + +def build_runtime(args): + try: + subprocess.run(["clang", "-v"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except FileNotFoundError: + print("ERROR: clang was not found on your system.") + if platform.system() == "Windows": + print("We recommend installing clang via the Visual Studio installer.") + # TODO(ben): Link to the Visual Studio download page (I have no internet right now) + elif platform.system() == "Darwin": + print("Run the following to install it:") + print() + print(" brew install llvm") + print() + exit(1) + + # TODO(ben): Check for xcode command line tools + + build_milepost("lib", args.release) + +def build_milepost(target, release): + print("Building milepost...") + with pushd("milepost"): + os.makedirs("bin", exist_ok=True) + os.makedirs("lib", exist_ok=True) + os.makedirs("resources", exist_ok=True) + + if target == "lib": + if platform.system() == "Darwin": + build_milepost_lib_mac(release) + else: + print(f"ERROR: can't build milepost for unknown platform '{platform.system()}'") + elif target == "test": + with pushd("examples/test_app"): + # TODO? + subprocess.run(["./build.sh"]) + elif target == "clean": + removeall("bin") + else: + print(f"ERROR: unrecognized milepost target '{target}'") + exit(1) + +def build_milepost_lib_mac(release): + sdk_dir = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" + + flags = ["-mmacos-version-min=10.15.4", "-maes"] + cflags = ["-std=c11"] + debug_flags = ["-O3"] if release else ["-g", "-DDEBUG", "-DLOG_COMPILE_DEBUG"] + ldflags = [f"-L{sdk_dir}/usr/lib", f"-F{sdk_dir}/System/Library/Frameworks/"] + includes = ["-Isrc", "-Isrc/util", "-Isrc/platform", "-Iext", "-Iext/angle_headers"] + + # compile metal shader + subprocess.run([ + "xcrun", "-sdk", "macosx", "metal", + # TODO: shaderFlagParam + "-fno-fast-math", "-c", + "-o", "lib/mtl_renderer.air", + "src/mtl_renderer.metal", + ], check=True) + subprocess.run([ + "xcrun", "-sdk", "macosx", "metallib", + "-o", "lib/mtl_renderer.metallib", + "lib/mtl_renderer.air", + ], check=True) + + # compile milepost. We use one compilation unit for all C code, and one + # compilation unit for all Objective-C code + subprocess.run([ + "clang", + *debug_flags, "-c", + "-o", "bin/milepost_c.o", + *cflags, *flags, *includes, + "src/milepost.c" + ], check=True) + subprocess.run([ + "clang", + *debug_flags, "-c", + "-o", "bin/milepost_objc.o", + *flags, *includes, + "src/milepost.m" + ], check=True) + + # build dynamic library + subprocess.run([ + "ld", + *ldflags, "-dylib", + "-o", "bin/libmilepost.dylib", + "bin/milepost_c.o", "bin/milepost_objc.o", + "-Llib", "-lc", + "-framework", "Carbon", "-framework", "Cocoa", "-framework", "Metal", "-framework", "QuartzCore", + "-weak-lEGL", "-weak-lGLESv2", + ], check=True) + + # change dependent libs path to @rpath + subprocess.run([ + "install_name_tool", + "-change", "./libEGL.dylib", "@rpath/libEGL.dylib", + "bin/libmilepost.dylib", + ], check=True) + subprocess.run([ + "install_name_tool", + "-change", "./libGLESv2.dylib", "@rpath/libGLESv2.dylib", + "bin/libmilepost.dylib", + ], check=True) + + # add executable path to rpath. Client executable can still add its own + # rpaths if needed, e.g. @executable_path/libs/ etc. + subprocess.run([ + "install_name_tool", + "-id", "@rpath/libmilepost.dylib", + "bin/libmilepost.dylib", + ], check=True) diff --git a/scripts/orca.py b/scripts/orca.py new file mode 100755 index 0000000..5b9c3b4 --- /dev/null +++ b/scripts/orca.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 + +import argparse + +from build_runtime import attach_build_runtime + +parser = argparse.ArgumentParser() + +subparsers = parser.add_subparsers(required=True, title='commands') +attach_build_runtime(subparsers) + +args = parser.parse_args() +args.func(args) diff --git a/scripts/utils.py b/scripts/utils.py new file mode 100644 index 0000000..d8ef8d8 --- /dev/null +++ b/scripts/utils.py @@ -0,0 +1,32 @@ +import glob +import os +import subprocess +import traceback + +from contextlib import contextmanager + +@contextmanager +def pushd(new_dir): + previous_dir = os.getcwd() + os.chdir(new_dir) + try: + yield + finally: + os.chdir(previous_dir) + + +def removeall(dir): + [os.remove(f) for f in glob.iglob("{}/*".format(dir), recursive=True)] + os.removedirs(dir) + + +def shellish(func): + def shellfunc(*args, **kwargs): + try: + func(*args, **kwargs) + except subprocess.CalledProcessError as err: + print() + print(f"ERROR (code {err.returncode}): The following command failed:") + print(" ".join(err.cmd)) + exit(err.returncode) + return shellfunc