orca/scripts/build_runtime.py

303 lines
9.3 KiB
Python
Raw Normal View History

import argparse
2023-07-08 22:21:01 +00:00
from datetime import datetime
2023-07-08 14:22:29 +00:00
import glob
import os
import platform
2023-07-08 14:22:29 +00:00
import urllib.request
import shutil
import subprocess
2023-07-08 14:22:29 +00:00
from zipfile import ZipFile
import checksum
from log import *
from utils import pushd, removeall
ANGLE_VERSION = "2023-07-05"
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):
ensure_programs()
ensure_angle()
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":
2023-07-08 22:21:01 +00:00
if platform.system() == "Windows":
build_milepost_lib_win(release)
elif platform.system() == "Darwin":
build_milepost_lib_mac(release)
else:
log_error(f"can't build milepost for unknown platform '{platform.system()}'")
exit(1)
elif target == "test":
with pushd("examples/test_app"):
# TODO?
subprocess.run(["./build.sh"])
elif target == "clean":
removeall("bin")
else:
log_error(f"unrecognized milepost target '{target}'")
exit(1)
2023-07-08 22:21:01 +00:00
def build_milepost_lib_win(release):
# TODO(ben): delete embed_text.py
embed_text_glsl("src\\glsl_shaders.h", "glsl_", [
"src\\glsl_shaders\\common.glsl",
"src\\glsl_shaders\\blit_vertex.glsl",
"src\\glsl_shaders\\blit_fragment.glsl",
"src\\glsl_shaders\\path_setup.glsl",
"src\\glsl_shaders\\segment_setup.glsl",
"src\\glsl_shaders\\backprop.glsl",
"src\\glsl_shaders\\merge.glsl",
"src\\glsl_shaders\\raster.glsl",
])
includes = [
"/I", "src",
"/I", "src/util",
"/I", "src/platform",
"/I", "ext",
"/I", "ext/angle_headers",
]
libs = [
"user32.lib",
"opengl32.lib",
"gdi32.lib",
"shcore.lib",
"delayimp.lib",
"dwmapi.lib",
"comctl32.lib",
"ole32.lib",
"shell32.lib",
"shlwapi.lib",
"/LIBPATH:./bin",
"libEGL.dll.lib",
"libGLESv2.dll.lib",
"/DELAYLOAD:libEGL.dll",
"/DELAYLOAD:libGLESv2.dll",
]
# TODO(ben): check for cl
subprocess.run([
"cl",
"/we4013", "/Zi", "/Zc:preprocessor",
"/DMP_BUILD_DLL",
"/std:c11",
*includes,
"src/milepost.c", "/Fo:bin/milepost.o",
"/LD", "/link",
"/MANIFEST:EMBED", "/MANIFESTINPUT:src/win32_manifest.xml",
*libs,
"/OUT:bin/milepost.dll",
"/IMPLIB:bin/milepost.dll.lib",
], check=True)
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)
def ensure_programs():
try:
subprocess.run(["clang", "-v"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except FileNotFoundError:
msg = log_error("clang was not found on your system.")
if platform.system() == "Windows":
msg.more("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":
msg.more("Run the following to install it:")
msg.more()
msg.more(" brew install llvm")
msg.more()
exit(1)
# TODO(ben): Check for xcode command line tools
def ensure_angle():
checkfiles = None
if platform.system() == "Windows":
checkfiles = [
2023-07-08 22:21:01 +00:00
"milepost/lib/libEGL.dll",
"milepost/lib/libGLESv2.dll",
]
elif platform.system() == "Darwin":
if platform.machine() == "arm64":
2023-07-08 22:21:01 +00:00
log_warning(f"automated ANGLE builds are not yet available for Apple silicon")
return
checkfiles = [
"milepost/lib/libEGL.dylib",
"milepost/lib/libGLESv2.dylib",
]
if checkfiles is None:
log_warning("could not verify if the correct version of ANGLE is present; the build will probably fail.")
return
angle_exists = True
for file in checkfiles:
key = file[0]
filepath = file[1]
if not os.path.isfile(filepath):
angle_exists = False
break
if not checksum.checkfile(key, filepath):
2023-07-08 22:21:01 +00:00
angle_exists = False
log_warning("wrong version of ANGLE libraries installed")
break
if not angle_exists:
2023-07-08 14:22:29 +00:00
download_angle()
def download_angle():
print("Downloading ANGLE...")
if platform.system() == "Windows":
build = "win"
extension = "dll"
elif platform.system() == "Darwin":
# TODO(ben): make universal dylibs
2023-07-08 22:21:01 +00:00
build = "macos-12"
extension = "dylib"
2023-07-08 14:22:29 +00:00
else:
log_error(f"could not automatically download ANGLE for unknown platform {platform.system()}")
return
2023-07-08 22:21:01 +00:00
os.makedirs("scripts/files", exist_ok=True)
filename = f"angle-{build}-{ANGLE_VERSION}.zip"
filepath = f"scripts/files/{filename}"
url = f"https://github.com/HandmadeNetwork/build-angle/releases/download/{ANGLE_VERSION}/{filename}"
2023-07-08 14:22:29 +00:00
with urllib.request.urlopen(url) as response:
2023-07-08 22:21:01 +00:00
with open(filepath, "wb") as out:
2023-07-08 14:22:29 +00:00
shutil.copyfileobj(response, out)
2023-07-08 22:21:01 +00:00
if not checksum.checkfile(filepath):
2023-07-08 14:22:29 +00:00
log_error(f"ANGLE download did not match checksum")
exit(1)
2023-07-08 22:21:01 +00:00
with ZipFile(filepath, "r") as anglezip:
2023-07-08 14:22:29 +00:00
anglezip.extractall(path="scripts/files")
for filepath in glob.glob(f"scripts/files/angle/bin/*.{extension}"):
shutil.copy(filepath, "milepost/lib")
2023-07-08 22:21:01 +00:00
def embed_text_glsl(outputpath, prefix, shaders):
output = open(outputpath, "w")
output.write("/*********************************************************************\n")
output.write("*\n")
output.write("*\tfile: %s\n" % os.path.basename(outputpath))
output.write("*\tnote: string literals auto-generated by build_runtime.py\n")
output.write("*\tdate: %s\n" % datetime.now().strftime("%d/%m%Y"))
output.write("*\n")
output.write("**********************************************************************/\n")
outSymbol = (os.path.splitext(os.path.basename(outputpath))[0]).upper()
output.write("#ifndef __%s_H__\n" % outSymbol)
output.write("#define __%s_H__\n" % outSymbol)
output.write("\n\n")
for fileName in shaders:
f = open(fileName, "r")
lines = f.read().splitlines()
output.write("//NOTE: string imported from %s\n" % fileName)
stringName = os.path.splitext(os.path.basename(fileName))[0]
output.write(f"const char* {prefix}{stringName} = ")
for line in lines:
output.write("\n\"%s\\n\"" % line)
output.write(";\n\n")
f.close()
output.write("#endif // __%s_H__\n" % outSymbol)
output.close()