#!/usr/bin/env python # Attempt to automatically generate a port for a Hackage module. from garstowlib.makeport import * from garstowlib.packages import get_scripts_dir, get_stow_dir from garstowlib.utils import memoised, parse_version import os, sys, re, glob, subprocess archive_url = "http://hackage.haskell.org/packages/archive/" ignore_packages = [ "HOC", "HOC-AppKit", "HOC-Foundation", "Win32", "special-functors", ] @memoised def get_ghc_packages(): """Get a list of packages that GHC ships with (and we thus don't need to bother listing as explicit dependencies).""" ghcdir = glob.glob(get_stow_dir() + "/packages/ghc/lib/*")[0] package_re = re.compile(r'^(.*)-[0-9.]+$') return [m.group(1) for m in map(package_re.match, os.listdir(ghcdir)) if m is not None] def parse_cabal_file(cabal_file): """Parse a Cabal file using the Haskell helper program.""" cmd = ["runhaskell", get_scripts_dir() + "/HsPortHelper.hs", cabal_file] output = subprocess.Popen(cmd, stdout = subprocess.PIPE).communicate()[0] return output.rstrip().split("\n") def make_garname(cabal_name): """Convert a Cabal name into a GARstow package name.""" if cabal_name in ("cairo", "gconf", "gio", "glade", "glib", "gnomevfs", "gtk", "gtkglext", "gtksourceview2", "mozembed", "soegtk", "svgcairo"): return "gtk2hs" else: return "hs-" + cabal_name def list_http_dir(url): """Return a list of files and subdirectories from an HTTP directory listing. This is a bit of a hack, but it'll probably work in most cases.""" # Make sure we have a trailing /, and remove duplicated ones (except ://). url = re.sub(r'([^:])/+', r'\1/', url + "/") # Run links, and extract URLs from its output (which will include a # complete list of links). entries = [] f = subprocess.Popen(["links", "-dump", url], stdout = subprocess.PIPE).stdout for l in f.readlines(): l = l.rstrip() for u in re.findall(r'(http://[^"\']+)', l): if u.startswith(url): entry = u[len(url):] if entry.endswith("/"): entry = entry[:-1] if not entry.startswith("?"): entries.append(entry) f.close() return entries def make_cabal_port(cabal_name): if cabal_name.startswith("hs-"): cabal_name = cabal_name[3:] version_prefix = "" m = re.match(r'^(.*)-([0-9.]+)$', cabal_name) if m is not None: (cabal_name, version_prefix) = m.group(1, 2) garname = make_garname(cabal_name) if version_prefix != "": garname += "-" + version_prefix if garname.startswith("hs-"): shortname = garname[3:] if os.access(shortname, os.F_OK): garname = shortname print "Making port " + garname + " for " + cabal_name + "..." if version_prefix != "": version_prefix += "." # Find the latest version of the package. # There should be a "latest" symlink in hackage_dir, but (as of May # 2009) many packages don't have that, or have it pointing to the wrong # version. hackage_dir = archive_url + cabal_name + "/" versions = [(parse_version(v), v) for v in list_http_dir(hackage_dir) if re.match(r'^[0-9]', v) is not None and v.startswith(version_prefix)] versions.sort() if versions == []: print "No versions found!" sys.exit(1) garversion = versions[-1][1] print " Using version: " + garversion # Grab and parse the Cabal file. cabal_file_url = hackage_dir + garversion + "/" + cabal_name + ".cabal" tmpfn = "/tmp/make-hs-port.%d" % os.getpid() subprocess.call(["wget", "-q", "-O", tmpfn, cabal_file_url]) parsed = parse_cabal_file(tmpfn) os.unlink(tmpfn) (description, home_url, cabal_deps) = parsed if home_url == "": home_url = None # Work out the dependencies (or a good approximation). deps = cabal_deps.split() ghc_packages = get_ghc_packages() need_deps = set(deps) - set(ghc_packages) - set(ignore_packages) libdeps = " ".join(["haskell/" + make_garname(dep) for dep in need_deps]) if libdeps == "": libdeps = None lines = [ VarLine("GARNAME", garname), VarLine("GARVERSION", garversion, mode = "update"), ] if version_prefix != "": lines += [VarLine("UPSTREAMNAME", cabal_name, mode = "update")] lines += [ VarLine("LIBDEPS", libdeps, mode = "merge-deps"), BlankLine(), VarLine("DESCRIPTION", description, mode = "update"), VarLine("HOME_URL", home_url, mode = "update"), BlankLine(), IncludeLine("../haskell.mk"), ] update_portfile(garname + "/Makefile", lines) if __name__ == "__main__": for name in sys.argv[1:]: make_cabal_port(name)