492 lines
14 KiB
Python
492 lines
14 KiB
Python
import argparse
|
|
from datetime import datetime
|
|
import glob
|
|
import os
|
|
import platform
|
|
import urllib.request
|
|
import shutil
|
|
import subprocess
|
|
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)
|
|
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":
|
|
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)
|
|
|
|
|
|
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)
|
|
|
|
|
|
def build_wasm3(release):
|
|
print("Building wasm3...")
|
|
|
|
os.makedirs("bin", exist_ok=True)
|
|
os.makedirs("bin/obj", exist_ok=True)
|
|
|
|
if platform.system() == "Windows":
|
|
build_wasm3_lib_win(release)
|
|
elif platform.system() == "Darwin":
|
|
build_wasm3_lib_mac(release)
|
|
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)
|
|
|
|
|
|
def build_wasm3_lib_mac(release):
|
|
includes = ["-I./ext/wasm3/source"]
|
|
debug_flags = ["-O2"] if release else ["-g"]
|
|
flags = [
|
|
*debug_flags,
|
|
"-foptimize-sibling-calls",
|
|
"-Wno-extern-initializer",
|
|
"-Dd_m3VerboseErrorMessages",
|
|
]
|
|
|
|
for f in glob.iglob("./ext/wasm3/source/*.c"):
|
|
name = os.path.splitext(os.path.basename(f))[0] + ".o"
|
|
subprocess.run([
|
|
"clang", "-c", *flags, *includes,
|
|
"-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":
|
|
build_orca_mac(release)
|
|
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")
|
|
|
|
gen_all_bindings()
|
|
|
|
# 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 build_orca_mac(release):
|
|
# copy libraries
|
|
shutil.copy("milepost/bin/mtl_renderer.metallib", "bin/")
|
|
shutil.copy("milepost/bin/libmilepost.dylib", "bin/")
|
|
shutil.copy("milepost/bin/libGLESv2.dylib", "bin/")
|
|
shutil.copy("milepost/bin/libEGL.dylib", "bin/")
|
|
|
|
includes = [
|
|
"-Isrc",
|
|
"-Isdk",
|
|
"-Imilepost/src",
|
|
"-Imilepost/src/util",
|
|
"-Imilepost/src/platform",
|
|
"-Iext/wasm3/source",
|
|
"-Imilepost/ext/",
|
|
]
|
|
libs = ["-Lbin", "-lmilepost", "-lwasm3"]
|
|
debug_flags = ["-O2"] if release else ["-g", "-DLOG_COMPILE_DEBUG"]
|
|
flags = [
|
|
*debug_flags,
|
|
"-mmacos-version-min=10.15.4",
|
|
"-maes",
|
|
]
|
|
|
|
gen_all_bindings()
|
|
|
|
# compile orca
|
|
subprocess.run([
|
|
"clang", *flags, *includes, *libs,
|
|
"-o", "bin/orca",
|
|
"src/main.c",
|
|
], check=True)
|
|
|
|
# fix libs imports
|
|
subprocess.run([
|
|
"install_name_tool", "-change",
|
|
"./bin/libmilepost.dylib", "@rpath/libmilepost.dylib", "bin/orca",
|
|
], check=True)
|
|
subprocess.run([
|
|
"install_name_tool", "-add_rpath",
|
|
"@executable_path/", "bin/orca",
|
|
], check=True)
|
|
|
|
|
|
def gen_all_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",
|
|
)
|
|
|
|
|
|
def ensure_programs():
|
|
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 = [
|
|
"milepost/lib/libEGL.dll",
|
|
"milepost/lib/libEGL.dll.lib",
|
|
"milepost/lib/libGLESv2.dll",
|
|
"milepost/lib/libGLESv2.dll.lib",
|
|
]
|
|
elif platform.system() == "Darwin":
|
|
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:
|
|
if not os.path.isfile(file):
|
|
ok = False
|
|
continue
|
|
if not checksum.checkfile(file):
|
|
ok = False
|
|
continue
|
|
|
|
return ok
|
|
|
|
|
|
def download_angle():
|
|
print("Downloading ANGLE...")
|
|
if platform.system() == "Windows":
|
|
build = "windows-2019"
|
|
extensions = ["dll", "lib"]
|
|
elif platform.system() == "Darwin":
|
|
build = "macos-jank"
|
|
extensions = ["dylib"]
|
|
else:
|
|
log_error(f"could not automatically download ANGLE for unknown platform {platform.system()}")
|
|
return
|
|
|
|
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}"
|
|
with urllib.request.urlopen(url) as response:
|
|
with open(filepath, "wb") as out:
|
|
shutil.copyfileobj(response, out)
|
|
|
|
if not checksum.checkfile(filepath):
|
|
log_error(f"ANGLE download did not match checksum")
|
|
exit(1)
|
|
|
|
print("Extracting ANGLE...")
|
|
with ZipFile(filepath, "r") as anglezip:
|
|
anglezip.extractall(path="scripts/files")
|
|
|
|
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")
|
|
|
|
|
|
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()
|