diff --git a/scripts/build_runtime.py b/scripts/build_runtime.py index e9b48f4..64c6012 100644 --- a/scripts/build_runtime.py +++ b/scripts/build_runtime.py @@ -3,7 +3,12 @@ import os import platform import subprocess -from utils import pushd, removeall, shellish +import checksum +from log import * +from utils import pushd, removeall + + +ANGLE_VERSION = "2023-07-05" def attach_build_runtime(subparsers): @@ -13,24 +18,12 @@ def attach_build_runtime(subparsers): 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 + ensure_programs() + ensure_angle() build_milepost("lib", args.release) + def build_milepost(target, release): print("Building milepost...") with pushd("milepost"): @@ -42,7 +35,8 @@ def build_milepost(target, release): if platform.system() == "Darwin": build_milepost_lib_mac(release) else: - print(f"ERROR: can't build milepost for unknown platform '{platform.system()}'") + log_error(f"can't build milepost for unknown platform '{platform.system()}'") + exit(1) elif target == "test": with pushd("examples/test_app"): # TODO? @@ -50,9 +44,10 @@ def build_milepost(target, release): elif target == "clean": removeall("bin") else: - print(f"ERROR: unrecognized milepost target '{target}'") + log_error(f"unrecognized milepost target '{target}'") exit(1) + def build_milepost_lib_mac(release): sdk_dir = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" @@ -123,3 +118,59 @@ def build_milepost_lib_mac(release): "-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 = [ + ["libEGL-win", "milepost/lib/libEGL.dll"], + ["libGLESv2-win", "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"], + ] + + 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): + log_error("wrong version of ANGLE libraries installed") + exit(1) + + if not angle_exists: + pass diff --git a/scripts/checksum.py b/scripts/checksum.py new file mode 100644 index 0000000..226fbc8 --- /dev/null +++ b/scripts/checksum.py @@ -0,0 +1,30 @@ +import hashlib +import json + +from log import * + + +def checkfile(key, 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}") + msg.more(f"file had checksum: {newsum}") + return False + sum = sums[key] + + if sum != newsum: + msg = log_warning(f"checksums did not match for {filepath}:") + msg.more(f"expected: {sum}") + msg.more(f" got: {newsum}") + return False + + return True + + +def filesum(filepath): + with open(filepath, "rb") as file: + return hashlib.sha256(file.read()).hexdigest() diff --git a/scripts/checksums.json b/scripts/checksums.json new file mode 100644 index 0000000..1c2126f --- /dev/null +++ b/scripts/checksums.json @@ -0,0 +1,6 @@ +{ + "libEGL-win": "3c8b22317664650deba704dd40bbd56447c579ee3a3de18a9c114449a883a36d", + "libGLESv2-win": "a10e0ce850a981b11d3d0f01a7efbf8ce46ac74e5fa763b5c43a80c4238da389", + "libEGL-mac-arm64": "227445d896047207d1dcef91a8182d886692bc470f402033a6f0831eacb82592", + "libGLESv2-mac-arm64": "c814948060494796cda4a3febd8652e1bbf0787a69c2f7e9afd41fc666dc91fe" +} diff --git a/scripts/log.py b/scripts/log.py new file mode 100644 index 0000000..1c28dbd --- /dev/null +++ b/scripts/log.py @@ -0,0 +1,80 @@ +import subprocess +import sys + + +errors = [] +warnings = [] + + +class Entry: + def __init__(self, msg): + self.msgs = [msg] + + def more(self, *msgs): + if len(msgs) == 0: + msgs = [""] + for msg in msgs: + print(msg) + self.msgs.append(msg) + + +def log_error(msg): + msg = f"ERROR: {msg}" + print(msg) + entry = Entry(msg) + errors.append(entry) + return entry + + +def log_warning(msg): + msg = f"WARNING: {msg}" + print(msg) + entry = Entry(msg) + warnings.append(entry) + return entry + + +def log_finish(): + if len(errors) + len(warnings) == 0: + print("Task completed successfully.") + return + + print() + + 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}:") + 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}:") + for entry in errors: + print("\n".join(entry.msgs)) + elif len(warnings) > 0: + print(f"Task failed with {warnings_str}:") + for entry in warnings: + print("\n".join(entry.msgs)) + + +def shellish(func): + def shellfunc(*args, **kwargs): + exitcode = 0 + try: + func(*args, **kwargs) + except subprocess.CalledProcessError as err: + msg = log_error(f"The following command failed with code {err.returncode}:") + msg.more(" ".join(err.cmd)) + exitcode = err.returncode + except SystemExit as err: + exitcode = err.code + except: + log_error(sys.exception()) + exitcode = 1 + finally: + log_finish() + exit(exitcode) + return shellfunc diff --git a/scripts/utils.py b/scripts/utils.py index d8ef8d8..72483dc 100644 --- a/scripts/utils.py +++ b/scripts/utils.py @@ -18,15 +18,3 @@ def pushd(new_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