diff --git a/scripts/build_runtime.py b/scripts/build_runtime.py index f1fd1fb..24d4b88 100644 --- a/scripts/build_runtime.py +++ b/scripts/build_runtime.py @@ -1,4 +1,5 @@ import argparse +from datetime import datetime import glob import os import platform @@ -36,7 +37,9 @@ def build_milepost(target, release): os.makedirs("resources", exist_ok=True) if target == "lib": - if platform.system() == "Darwin": + 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()}'") @@ -52,6 +55,60 @@ def build_milepost(target, release): 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", + ] + + # 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" @@ -145,21 +202,17 @@ def ensure_angle(): checkfiles = None if platform.system() == "Windows": checkfiles = [ - ["libEGL-win", "milepost/lib/libEGL.dll"], - ["libGLESv2-win", "milepost/lib/libGLESv2.dll"], + "milepost/lib/libEGL.dll", + "milepost/lib/libGLESv2.dll", ] elif platform.system() == "Darwin": - # TODO(ben): Do we need specific builds of ANGLE for each macOS version? if platform.machine() == "arm64": - checkfiles = [ - ["libEGL-mac-arm64", "milepost/lib/libEGL.dylib"], - ["libGLESv2-mac-arm64", "milepost/lib/libGLESv2.dylib"], - ] - else: - checkfiles = [ - ["libEGL-mac-x64", "milepost/lib/libEGL.dylib"], - ["libGLESv2-mac-x64", "milepost/lib/libGLESv2.dylib"], - ] + 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.") @@ -173,8 +226,9 @@ def ensure_angle(): angle_exists = False break if not checksum.checkfile(key, filepath): - log_error("wrong version of ANGLE libraries installed") - exit(1) + angle_exists = False + log_warning("wrong version of ANGLE libraries installed") + break if not angle_exists: download_angle() @@ -184,33 +238,65 @@ def download_angle(): print("Downloading ANGLE...") if platform.system() == "Windows": build = "win" - checksumkey = "angle.zip-win" extension = "dll" elif platform.system() == "Darwin": - extension = "dylib" - build = "macos-12" - checksumkey = "angle.zip-mac" - # TODO(ben): make universal dylibs - if platform.machine() == "arm64": - log_error(f"automated ANGLE builds are not yet available for Apple silicon") - return + build = "macos-12" + extension = "dylib" else: log_error(f"could not automatically download ANGLE for unknown platform {platform.system()}") return - url = f"https://github.com/HandmadeNetwork/build-angle/releases/download/{ANGLE_VERSION}/angle-{build}-{ANGLE_VERSION}.zip" + 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: - os.makedirs("scripts/files", exist_ok=True) - with open("scripts/files/angle.zip", "wb") as out: + with open(filepath, "wb") as out: shutil.copyfileobj(response, out) - if not checksum.checkfile(checksumkey, "scripts/files/angle.zip"): + if not checksum.checkfile(filepath): log_error(f"ANGLE download did not match checksum") exit(1) - with ZipFile("scripts/files/angle.zip", "r") as anglezip: + with ZipFile(filepath, "r") as anglezip: anglezip.extractall(path="scripts/files") for filepath in glob.glob(f"scripts/files/angle/bin/*.{extension}"): 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() diff --git a/scripts/checksum.py b/scripts/checksum.py index 226fbc8..e404081 100644 --- a/scripts/checksum.py +++ b/scripts/checksum.py @@ -4,17 +4,17 @@ import json from log import * -def checkfile(key, filepath): +def checkfile(filepath): newsum = filesum(filepath) sums = {} with open("scripts/checksums.json", "r") as sumsfile: sums = json.loads(sumsfile.read()) - if key not in sums: - msg = log_warning(f"no checksum saved for {key}") + if filepath not in sums: + msg = log_warning(f"no checksum saved for file {filepath}") msg.more(f"file had checksum: {newsum}") return False - sum = sums[key] + sum = sums[filepath] if sum != newsum: msg = log_warning(f"checksums did not match for {filepath}:") diff --git a/scripts/checksums.json b/scripts/checksums.json index d3289bf..d68ec7b 100644 --- a/scripts/checksums.json +++ b/scripts/checksums.json @@ -1,8 +1,8 @@ { - "angle.zip-win": "pizza", - "angle.zip-mac-x64": "a3422c456278ff037ef89a7808e0ba256d972d4832d5272fc3d4aa4f7912c1e0", - "libEGL-win": "3c8b22317664650deba704dd40bbd56447c579ee3a3de18a9c114449a883a36d", - "libGLESv2-win": "a10e0ce850a981b11d3d0f01a7efbf8ce46ac74e5fa763b5c43a80c4238da389", - "libEGL-mac": "227445d896047207d1dcef91a8182d886692bc470f402033a6f0831eacb82592", - "libGLESv2-mac": "c814948060494796cda4a3febd8652e1bbf0787a69c2f7e9afd41fc666dc91fe" + "scripts/files/angle-win-2023-07-05": "pizza", + "scripts/files/angle-mac-2023-07-05": "a3422c456278ff037ef89a7808e0ba256d972d4832d5272fc3d4aa4f7912c1e0", + "milepost/lib/libEGL.dll": "3c8b22317664650deba704dd40bbd56447c579ee3a3de18a9c114449a883a36d", + "milepost/lib/libGLESv2.dll": "a10e0ce850a981b11d3d0f01a7efbf8ce46ac74e5fa763b5c43a80c4238da389", + "milepost/lib/libEGL.dylib": "227445d896047207d1dcef91a8182d886692bc470f402033a6f0831eacb82592", + "milepost/lib/libGLESv2.dylib": "c814948060494796cda4a3febd8652e1bbf0787a69c2f7e9afd41fc666dc91fe" } diff --git a/scripts/log.py b/scripts/log.py index 1c28dbd..3cb7911 100644 --- a/scripts/log.py +++ b/scripts/log.py @@ -34,28 +34,29 @@ def log_warning(msg): return entry -def log_finish(): - if len(errors) + len(warnings) == 0: +def log_finish(success): + if success and len(errors) + len(warnings) == 0: print("Task completed successfully.") return print() + result_str = "succeeded" if success else "failed" errors_str = "1 error" if len(errors) == 1 else f"{len(errors)} errors" warnings_str = "1 warning" if len(warnings) == 1 else f"{len(warnings)} warnings" if len(errors) > 0 and len(warnings) > 0: - print(f"Task failed with {errors_str} and {warnings_str}:") + print(f"Task {result_str} with {errors_str} and {warnings_str}:") for entry in warnings: print("\n".join(entry.msgs)) for entry in errors: print("\n".join(entry.msgs)) elif len(errors) > 0: - print(f"Task failed with {errors_str}:") + print(f"Task {result_str} with {errors_str}:") for entry in errors: print("\n".join(entry.msgs)) elif len(warnings) > 0: - print(f"Task failed with {warnings_str}:") + print(f"Task {result_str} with {warnings_str}:") for entry in warnings: print("\n".join(entry.msgs)) @@ -75,6 +76,6 @@ def shellish(func): log_error(sys.exception()) exitcode = 1 finally: - log_finish() + log_finish(exitcode == 0) exit(exitcode) return shellfunc