Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
4.6 kB · 103 lines
Python
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104"""Compile patch/src and refresh patch/prebuilt/. Only needed after changing patch/src.
python tools/build_patch.py [facade-install-folder]
Players never run this: the compiled classes are committed in patch/prebuilt/and tools/install_patch.py uses those. It needs a JDK (9 to 21) on PATH and aFacade install to compile against.
1. Compiles patch/src with javac, targeting Java 8, the oldest it can emit.2. Retargets the classes to Java 1.4 (class file version 48), after checking they reference nothing a 1.4 runtime lacks. That is sound because the sources stick to 1.4 language features and APIs; the StackMapTable attribute javac adds is unknown to old JVMs, which ignore unknown attributes.3. Copies the classes that ship into patch/prebuilt/ and records a digest of the sources beside them, so a test can tell when they are out of date.
Run tools/test_patch.py afterwards: it executes the result on the game's ownJVM, which is the real proof."""import hashlibimport shutilimport subprocessimport sys
from classfile import INVOKE_DYNAMIC_TAGS, JAVA_1_4_MAJOR, ClassFilefrom facade_install import BUILD_CLASSES, PREBUILT_CLASSES, REPO, from_argsfrom original_parser import renamed_original_parserfrom patch_manifest import GENERATED, PREBUILT
SOURCES = REPO / "patch" / "src"DIGEST_FILE = PREBUILT_CLASSES / "SOURCES.sha256"
# Anything here in a constant pool means the class would die on Java 1.4. It# is a list of the usual suspects (string "+", autoboxing, for-each, enums,# try-with-resources, lambdas), not a proof: a 1.5+ method on an old class,# such as String.format, gets through. tools/test_patch.py is the real check.# Method names and descriptors are separate pool entries, so the autoboxing# markers are the valueOf descriptors on their own.TOO_NEW = ("java/lang/StringBuilder", "java/lang/invoke/", "java/util/Objects", "java/lang/AutoCloseable", "addSuppressed", "java/lang/Iterable", "java/lang/Enum", "(I)Ljava/lang/Integer;", "(J)Ljava/lang/Long;", "(Z)Ljava/lang/Boolean;", "(C)Ljava/lang/Character;", "(D)Ljava/lang/Double;")
def sources_digest(): """One hash over every source file, independent of line endings.""" digest = hashlib.sha256() for path in sorted(SOURCES.rglob("*.java")): digest.update(path.relative_to(SOURCES).as_posix().encode()) digest.update(path.read_bytes().replace(b"\r\n", b"\n")) return digest.hexdigest()
def compile_sources(classes): sources = sorted(SOURCES.rglob("*.java")) command = ["javac", "--release", "8", "-nowarn", "-Xlint:-options", "-cp", "%s;%s" % (BUILD_CLASSES, classes), "-d", str(BUILD_CLASSES)] try: result = subprocess.run(command + [str(s) for s in sources], capture_output=True, text=True) except FileNotFoundError: sys.exit("javac not found on PATH; install a JDK (9 to 21)") if result.returncode != 0: sys.exit("javac failed:\n%s%s" % (result.stdout, result.stderr)) return [BUILD_CLASSES / s.relative_to(SOURCES).with_suffix(".class") for s in sources]
def retarget(path): parsed = ClassFile(path.read_bytes()) pool_text = "\n".join(parsed.utf8_strings()) problems = [marker for marker in TOO_NEW if marker in pool_text] if parsed.tags() & INVOKE_DYNAMIC_TAGS: problems.append("invokedynamic/method handles") if problems: sys.exit("%s uses things Java 1.4 lacks: %s" % (path.name, ", ".join(problems))) parsed.major, parsed.minor = JAVA_1_4_MAJOR, 0 path.write_bytes(parsed.to_bytes())
def main(): install = from_args(sys.argv) if BUILD_CLASSES.exists(): shutil.rmtree(BUILD_CLASSES) # The sources call the original parser by its new name, so it has to exist # to compile against (and test_patch.py runs from build/classes). original = BUILD_CLASSES / GENERATED[0] original.parent.mkdir(parents=True, exist_ok=True) original.write_bytes(renamed_original_parser(install.classes)) for path in compile_sources(install.classes): retarget(path)
if PREBUILT_CLASSES.exists(): shutil.rmtree(PREBUILT_CLASSES) for name in PREBUILT: target = PREBUILT_CLASSES / name target.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(BUILD_CLASSES / name, target) DIGEST_FILE.write_text(sources_digest() + "\n", encoding="ascii") print("built %d classes for Java 1.4 into %s; commit that folder" % (len(PREBUILT), PREBUILT_CLASSES.relative_to(REPO)))
if __name__ == "__main__": main()