#!/usr/bin/env python # Attempt to automatically generate a port for a Hackage module. # Copyright 2009, 2010, 2011 Adam Sampson from garstowlib.config import gar_dir from garstowlib.makeport import * from garstowlib.packages import get_scripts_dir, get_stow_dir, get_package_names, get_owning_package from garstowlib.utils import memoised, parse_version, list_http_dir import os, sys, re, glob, subprocess archive_url = "http://hackage.haskell.org/packages/archive/" ignore_packages = [ "HOC", "HOC-AppKit", "HOC-Foundation", "Win32", "integer", "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 get_cabal_url(cabal_name): """Return the URL of the Cabal file for a package.""" if cabal_name == "haskell-platform": return "http://code.galois.com/darcs/haskell-platform/haskell-platform.cabal" # 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] versions.sort() if len(versions) == 0: print "No versions found!" return True version = versions[-1][1] print " Using version: " + version return hackage_dir + version + "/" + cabal_name + ".cabal" 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[:-1].split("\n") def make_garname(cabal_name): """Convert a Cabal name into a GARstow package name.""" # Try to match the name inside the haskell/ category first, since some # (e.g. tar, html) are duplicated elsewhere. for pattern in ("/haskell/hs-", "/haskell/", "/*/"): dirs = glob.glob(gar_dir + pattern + cabal_name) if dirs != []: return "/".join(dirs[0].split("/")[-2:]) # No match? Must be a new one. return "haskell/hs-" + cabal_name def convert_deps(cabal_deps): """Given a space-separated list of dependencies from Cabal, return the corresponding GARstow package names.""" deps = cabal_deps.split() ghc_packages = get_ghc_packages() need_deps = set(deps) - set(ghc_packages) - set(ignore_packages) if len(need_deps) == 0: gardeps = None else: gardeps = " ".join([make_garname(dep) for dep in need_deps]) return (need_deps, gardeps) def convert_tools(cabal_tools): """Given a space-separated list of tools from Cabal, return the corresponding GARstow package names.""" gardeps = set() for tool in cabal_tools.split(): path = get_stow_dir() + "/bin/" + tool wrong_name = make_garname(tool) if os.access(path, os.F_OK): gardeps.add(get_package_names()[get_owning_package(path)]) elif os.access(gar_dir + "/" + wrong_name, os.F_OK): # Because cabal doesn't actually check for the tool # dependencies, some Cabal files accidentally list # packages rather than binaries here. gardeps.add(wrong_name) else: die("Tool not installed: ", tool) if len(gardeps) == 0: return None else: return " ".join(gardeps) def make_cabal_port(cabal_name): # Turn "wherever/hs-foo/" into "foo". m = re.match(r'^.*?(?:hs-)?([^/]+)/*', cabal_name) cabal_name = m.group(1) garname = make_garname(cabal_name) print "Making port " + garname + " for " + cabal_name + "..." # Grab and parse the Cabal file. tmpfn = "/tmp/make-hs-port.%d" % os.getpid() subprocess.call(["wget", "-q", "-O", tmpfn, get_cabal_url(cabal_name)]) parsed = parse_cabal_file(tmpfn) os.unlink(tmpfn) (version, description, home_url, cabal_deps, cabal_tools) = parsed if home_url == "": home_url = None (need_libs, libdeps) = convert_deps(cabal_deps) builddeps = convert_tools(cabal_tools) if cabal_name == "haskell-platform": # The platform's tool dependencies are LIBDEPS for # GARstow's purposes. libdeps += " " + builddeps builddeps = None include = "../../gar.mk" elif garname.startswith("haskell/"): include = "../haskell.mk" else: include = "../../haskell/haskell.mk" lines = [ VarLine("GARNAME", os.path.basename(garname)), VarLine("GARVERSION", version, mode = "update"), VarLine("LIBDEPS", libdeps, mode = "merge-deps"), VarLine("BUILDDEPS", builddeps, mode = "merge-deps"), BlankLine(), VarLine("DESCRIPTION", description, mode = "update"), VarLine("HOME_URL", home_url, mode = "update"), BlankLine(), IncludeLine(include), ] update_portfile(gar_dir + "/" + garname + "/Makefile", lines) failed = False for dep in need_libs: port = make_garname(dep) if not os.access(gar_dir + "/" + port, os.F_OK): print garname + " needs new package: " + port if make_cabal_port(dep): failed = True return failed if __name__ == "__main__": failed = False for name in sys.argv[1:]: if make_cabal_port(name): failed = True if failed: sys.exit(1)