#!/usr/bin/env python import os import sys import zipfile PATHS = {"libdir": "/usr/lib/gcj", "gcj": "/usr/bin/gcj", "dbtool": "/usr/bin/gcj-dbtool"} GCJFLAGS = os.environ.get("RPM_OPT_FLAGS", "").split() + [ "-fPIC", "-findirect-dispatch"] LDFLAGS = ["-Wl,-Bsymbolic"] class Error(Exception): pass def aot_compile_rpm(basedir, libdir): """Search basedir for jarfiles, then generate solibs and class mappings for them all in libdir.""" dstdir = os.path.join(basedir, libdir.strip(os.sep)) if not os.path.isdir(dstdir): os.makedirs(dstdir) for jar in weed_jars(find_jars(basedir)): aot_compile_jar(jar, dstdir, libdir) def find_jars(dir): """Return a list of every jarfile under a directory. Goes on magic rather than file extension so we hit wars, ears, rars and anything else they cooked up lately.""" def visit(jars, dir, items): for item in items: path = os.path.join(dir, item) if os.path.islink(path) or not os.path.isfile(path): continue # could use zipfile.is_zipfile() but this is quicker if open(path, "r").read(2) != "PK": continue zf = zipfile.ZipFile(path, "r") try: zf.getinfo("META-INF/MANIFEST.MF") except KeyError: continue assert not jars.has_key(item) jars[item] = zf jars = {} os.path.walk(dir, visit, jars) jars = [(jar.filename, jar) for jar in jars.values()] jars.sort() return [jar for path, jar in jars] def weed_jars(jars): """Remove any jarfiles that are completely contained within another. This is more common than you'd think, and we only need one nativified copy of each class after all.""" while True: for jar1 in jars: for jar2 in jars: if jar1 is jar2: continue for item2 in jar2.infolist(): try: item1 = jar1.getinfo(item2.filename) except KeyError: break if item1.CRC != item2.CRC: break else: warn("subsetted %s" % jar2.filename) jars.remove(jar2) break else: continue break else: break continue return jars def aot_compile_jar(jar, dir, libdir): """Generate the shared library and class mapping for one jarfile. If the shared library already exists then it will not be overwritten. This is to allow optimizer failures and the like to be worked around.""" basename = os.path.basename(jar.filename) soname = os.path.join(dir, basename + ".so") dbname = os.path.join(dir, basename + ".db") if os.path.exists(soname): warn("not recreating %s" % soname) else: sources = split_jarfile(jar, dir) if sources == [jar.filename]: # compile and link system([PATHS["gcj"], "-shared"] + GCJFLAGS + LDFLAGS + [jar.filename, "-o", soname]) else: # compile objects = [] for source in sources: object = os.path.join(dir, os.path.basename(source) + ".o") system([PATHS["gcj"], "-c"] + GCJFLAGS + [source, "-o", object]) objects.append(object) # link system([PATHS["gcj"], "-shared"] + GCJFLAGS + LDFLAGS + objects + ["-o", soname]) # clean up for item in sources + objects: os.unlink(item) # dbtool dbname = os.path.join(dir, basename + ".db") system([PATHS["dbtool"], "-n", dbname, "64"]) system([PATHS["dbtool"], "-f", dbname, jar.filename, os.path.join(libdir, basename + ".so")]) def split_jarfile(src, dir, split = 1500): """In order to avoid #158308 we must split large jarfiles on PPC.""" if os.environ.get("RPM_ARCH") != "ppc": return [src.filename] items = src.infolist() if len([i for i in items if i.filename.endswith(".class")]) < split: return [src.filename] warn("splitting %s" % src.filename) jarfiles, dst = [], None for item in items: if (dst is None or item.filename.endswith(".class") and size >= split): if dst is not None: dst.close() path = os.path.join(dir, "%s.%d.jar" % ( os.path.basename(src.filename), len(jarfiles) + 1)) jarfiles.append(path) dst = zipfile.ZipFile(path, "w", zipfile.ZIP_STORED) size = 0 dst.writestr(item, src.read(item.filename)) size += 1 dst.close() return jarfiles def system(command): """Execute a command.""" prefix = os.environ.get("PS4", "+ ") prefix = prefix[0] + prefix print >>sys.stderr, prefix + " ".join(command) status = os.spawnv(os.P_WAIT, command[0], command) if status > 0: raise Error, "%s exited with code %d" % status elif status < 0: raise Error, "%s killed by signal %d" % -status def warn(msg): """Print a warning message.""" print >>sys.stderr, "%s: warning: %s" % ( os.path.basename(sys.argv[0]), msg) if __name__ == "__main__": try: name = os.environ.get("RPM_PACKAGE_NAME") if name is None: raise Error, "this script is designed for use in rpm specfiles" arch = os.environ.get("RPM_ARCH") if arch == "noarch": raise Error, "cannot be used on noarch packages" buildroot = os.environ.get("RPM_BUILD_ROOT") if buildroot in (None, "/"): raise Error, "bad $RPM_BUILD_ROOT" aot_compile_rpm(buildroot, os.path.join(PATHS["libdir"], name)) except Error, e: print >>sys.stderr, "%s: error: %s" % ( os.path.basename(sys.argv[0]), e)