orca/scripts/build_runtime.py

439 lines
13 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 bindgen import bindgen
from bindgen2 import bindgen2
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)
2023-07-19 23:14:27 +00:00
build_wasm3(args.release)
build_orca(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",
]
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)
2023-07-19 23:14:27 +00:00
def build_wasm3(release):
print("Building wasm3...")
os.makedirs("bin", exist_ok=True)
2023-07-23 15:18:25 +00:00
os.makedirs("bin/obj", exist_ok=True)
2023-07-19 23:14:27 +00:00
if platform.system() == "Windows":
build_wasm3_lib_win(release)
elif platform.system() == "Darwin":
2023-07-23 15:18:25 +00:00
build_wasm3_lib_mac(release)
2023-07-19 23:14:27 +00:00
else:
log_error(f"can't build wasm3 for unknown platform '{platform.system()}'")
exit(1)
def build_wasm3_lib_win(release):
for f in glob.iglob(".\\ext\\wasm3\\source\\*.c"):
name = os.path.splitext(os.path.basename(f))[0]
subprocess.run([
"cl", "/nologo",
"/Zi", "/Zc:preprocessor", "/c",
*(["/O2"] if release else []),
f"/Fo:bin\\obj\\{name}.obj",
"/I", ".\\ext\\wasm3\\source",
f,
], check=True)
subprocess.run([
"lib", "/nologo", "/out:bin\\wasm3.lib",
"bin\\obj\\*.obj",
], check=True)
2023-07-23 15:18:25 +00:00
def build_wasm3_lib_mac(release):
for f in glob.iglob("./ext/wasm3/source/*.c"):
name = os.path.splitext(os.path.basename(f))[0] + ".o"
subprocess.run([
"clang", "-c",
"-g", *(["/O2"] if release else []),
"-foptimize-sibling-calls", "-Wno-extern-initializer", "-Dd_m3VerboseErrorMessages",
"-I./ext/wasm3/source",
"-o", f"./bin/obj/{name}",
f,
], check=True)
subprocess.run(["ar", "-rcs", "./bin/libwasm3.a", *glob.glob("./bin/obj/*.o")], check=True)
subprocess.run(["rm", "-rf", "./bin/obj"], check=True)
def build_orca(release):
print("Building Orca...")
os.makedirs("bin", exist_ok=True)
if platform.system() == "Windows":
build_orca_win(release)
elif platform.system() == "Darwin":
2023-07-23 15:18:25 +00:00
log_error("can't yet build Orca on Mac")
exit(1)
else:
log_error(f"can't build Orca for unknown platform '{platform.system()}'")
exit(1)
def build_orca_win(release):
pthread_dir = "..\\vcpkg\\packages\\pthreads_x64-windows"
# copy libraries
shutil.copy("milepost\\bin\\milepost.dll", "bin")
shutil.copy("milepost\\bin\\milepost.dll.lib", "bin")
shutil.copy(os.path.join(pthread_dir, "bin\\pthreadVC3.dll"), "bin")
# generate wasm3 api bindings
bindgen("core", "src")
bindgen("gles", "src")
bindgen2("canvas", "src\\canvas_api.json",
guest_stubs="sdk\\orca_surface.c",
guest_include="graphics.h",
wasm3_bindings="src\\canvas_api_bind_gen.c",
)
bindgen2("clock", "src\\clock_api.json",
guest_stubs="sdk\\orca_clock.c",
guest_include="platform_clock.h",
wasm3_bindings="src\\clock_api_bind_gen.c",
)
bindgen2("io", "src\\io_api.json",
guest_stubs="sdk\\io_stubs.c",
wasm3_bindings="src\\io_api_bind_gen.c",
)
# compile orca
pthread_include = os.path.join(pthread_dir, "include")
includes = [
"/I", "src",
"/I", "sdk",
"/I", "ext\wasm3\source",
"/I", "milepost\src",
"/I", "milepost\ext",
"/I", pthread_include,
]
pthread_lib = os.path.join(pthread_dir, "lib")
libs = [
"/LIBPATH:bin",
f"/LIBPATH:{pthread_lib}",
"milepost.dll.lib",
"wasm3.lib",
"pthreadVC3.lib",
]
subprocess.run([
"cl",
"/Zi", "/Zc:preprocessor", "/std:c11",
*includes,
"src\\main.c",
"/link", *libs,
"/out:bin\\orca.exe",
], check=True)
def ensure_programs():
2023-07-15 21:23:05 +00:00
if platform.system() == "Windows":
try:
subprocess.run(["cl"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except FileNotFoundError:
msg = log_error("MSVC was not found on your system.")
msg.more("If you have already installed Visual Studio, make sure you are running in a")
msg.more("Visual Studio command prompt or you have run vcvarsall.bat. Otherwise, download")
msg.more("and install Visual Studio: https://visualstudio.microsoft.com/")
exit(1)
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.")
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():
if not verify_angle():
download_angle()
print("Verifying ANGLE download...")
if not verify_angle():
log_error("automatic ANGLE download failed")
exit(1)
def verify_angle():
checkfiles = None
if platform.system() == "Windows":
checkfiles = [
2023-07-08 22:21:01 +00:00
"milepost/lib/libEGL.dll",
2023-07-15 23:39:44 +00:00
"milepost/lib/libEGL.dll.lib",
2023-07-08 22:21:01 +00:00
"milepost/lib/libGLESv2.dll",
2023-07-15 23:39:44 +00:00
"milepost/lib/libGLESv2.dll.lib",
]
elif platform.system() == "Darwin":
2023-07-08 22:21:01 +00:00
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")
return False
ok = True
for file in checkfiles:
2023-07-15 21:23:05 +00:00
if not os.path.isfile(file):
ok = False
continue
2023-07-15 21:23:05 +00:00
if not checksum.checkfile(file):
ok = False
continue
return ok
2023-07-08 14:22:29 +00:00
def download_angle():
print("Downloading ANGLE...")
if platform.system() == "Windows":
2023-07-15 21:23:05 +00:00
build = "windows-2019"
2023-07-15 23:39:44 +00:00
extensions = ["dll", "lib"]
2023-07-08 14:22:29 +00:00
elif platform.system() == "Darwin":
2023-07-22 23:16:31 +00:00
build = "macos-jank"
2023-07-15 23:39:44 +00:00
extensions = ["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-15 21:23:05 +00:00
print("Extracting ANGLE...")
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")
2023-07-15 23:39:44 +00:00
os.makedirs("milepost/lib", exist_ok=True)
for angleDir in ["bin", "lib"]:
for ext in extensions:
for filepath in glob.glob(f"scripts/files/angle/{angleDir}/*.{ext}"):
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()