Blame climbing-nemesis.py

William Benton a6a8fa9
#!/usr/bin/env python
William Benton a6a8fa9
William Benton a6a8fa9
import xml.etree.ElementTree as ET
William Benton a6a8fa9
import argparse
William Benton a6a8fa9
import StringIO
William Benton a6a8fa9
import re
William Benton a6a8fa9
import subprocess
William Benton a6a8fa9
import logging
William Benton a6a8fa9
William Benton a6a8fa9
from os.path import exists as pathexists
William Benton a6a8fa9
from os.path import realpath
William Benton a6a8fa9
from os.path import join as pathjoin
William Benton a6a8fa9
from os import makedirs
William Benton a6a8fa9
from os import symlink
William Benton a6a8fa9
from os import remove as rmfile
William Benton a6a8fa9
from shutil import copyfile
William Benton a6a8fa9
William Benton a6a8fa9
class Artifact(object):
William Benton a6a8fa9
    def __init__(self, a, g, v):
William Benton a6a8fa9
        self.artifact = a
William Benton a6a8fa9
        self.group = g
William Benton a6a8fa9
        self.version = v
William Benton a6a8fa9
    
William Benton a6a8fa9
    @classmethod
William Benton a6a8fa9
    def fromCoords(k, coords):
William Benton a6a8fa9
        g,a,v = coords.split(":")
William Benton a6a8fa9
        return k(a, g, v)
William Benton a6a8fa9
    
William Benton a6a8fa9
    @classmethod
William Benton a6a8fa9
    def fromSubtree(k, t, ns):
William Benton a6a8fa9
        a = t.find("./%sartifactId" % ns).text
William Benton a6a8fa9
        g = t.find("./%sgroupId" % ns).text
William Benton a6a8fa9
        v = t.find("./%sversion" % ns).text
William Benton a6a8fa9
        return k(a, g, v)
William Benton a6a8fa9
    
William Benton a6a8fa9
    def contains(self, substrings):
William Benton a6a8fa9
        for s in substrings:
William Benton a6a8fa9
            if s in self.artifact or s in self.group:
William Benton a6a8fa9
                cn_debug("ignoring %r because it contains %s" % (self, s))
William Benton a6a8fa9
                return True
William Benton a6a8fa9
        if len(substrings) > 0:
William Benton a6a8fa9
            cn_debug("not ignoring %r; looked for %r" % (self, substrings))
William Benton a6a8fa9
        return False
William Benton a6a8fa9
    
William Benton a6a8fa9
    def __repr__(self):
William Benton a6a8fa9
        return "%s:%s:%s" % (self.group, self.artifact, self.version)
William Benton a6a8fa9
William Benton a6a8fa9
class DummyPOM(object):
William Benton a6a8fa9
    def __init__(self, groupID=None, artifactID=None, version=None):
William Benton a6a8fa9
        self.groupID = groupID
William Benton a6a8fa9
        self.artifactID = artifactID
William Benton a6a8fa9
        self.version = version
William Benton a6a8fa9
        self.deps = []
William Benton a6a8fa9
William Benton a6a8fa9
def interestingDep(dt, namespace):
William Benton a6a8fa9
    if len(dt.findall("./%soptional" % namespace)) != 0:
William Benton a6a8fa9
        cn_debug("ignoring optional dep %r" % Artifact.fromSubtree(dt, namespace))
William Benton a6a8fa9
        return False
William Benton a6a8fa9
    if [e for e in dt.findall("./%sscope" % namespace) if e.text == "test"] != []:
William Benton a6a8fa9
        cn_debug("ignoring test dep %r" % Artifact.fromSubtree(dt, namespace))
William Benton a6a8fa9
        return False
William Benton a6a8fa9
    return True
William Benton a6a8fa9
William Benton a6a8fa9
class POM(object):
William Benton a6a8fa9
    def __init__(self, filename, suppliedGroupID=None, suppliedArtifactID=None, ignored_deps=[], override=None, extra_deps=[]):
William Benton a6a8fa9
        self.filename = filename
William Benton a6a8fa9
        self.sGroupID = suppliedGroupID
William Benton a6a8fa9
        self.sArtifactID = suppliedArtifactID
William Benton a6a8fa9
        self.logger = logging.getLogger("com.freevariable.climbing-nemesis")
William Benton a6a8fa9
        self.deps = []
William Benton a6a8fa9
        self.ignored_deps = ignored_deps
William Benton a6a8fa9
        self.extra_deps = extra_deps
William Benton a6a8fa9
        cn_debug("POM:  extra_deps is %r" % extra_deps)
William Benton a6a8fa9
        self._parsePom()
William Benton a6a8fa9
        self.claimedGroup, self.claimedArtifact  = override is not None and override or (self.groupID, self.artifactID)
William Benton a6a8fa9
    
William Benton a6a8fa9
    def _parsePom(self):
William Benton a6a8fa9
        tree = ET.parse(self.filename)
William Benton a6a8fa9
        project = tree.getroot()
William Benton a6a8fa9
        self.logger.info("parsing POM %s", self.filename)
William Benton a6a8fa9
        self.logger.debug("project tag is '%s'", project.tag)
William Benton a6a8fa9
        tagmatch = re.match("[{](.*)[}].*", project.tag)
William Benton a6a8fa9
        namespace = tagmatch and "{%s}" % tagmatch.groups()[0] or ""
William Benton a6a8fa9
        self.logger.debug("looking for '%s'", ("./%sgroupId" % namespace))
William Benton a6a8fa9
        groupIDtag = project.find("./%sgroupId" % namespace) 
William Benton a6a8fa9
        if groupIDtag is None:
William Benton a6a8fa9
            groupIDtag = project.find("./%sparent/%sgroupId" % (namespace,namespace))
William Benton a6a8fa9
        
William Benton a6a8fa9
        versiontag = project.find("./%sversion" % namespace)
William Benton a6a8fa9
        if versiontag is None:
William Benton a6a8fa9
            versiontag = project.find("./%sparent/%sversion" % (namespace,namespace))
William Benton a6a8fa9
        self.logger.debug("group ID tag is '%s'", groupIDtag)
William Benton a6a8fa9
        self.groupID = groupIDtag.text
William Benton a6a8fa9
        self.artifactID = project.find("./%sartifactId" % namespace).text
William Benton a6a8fa9
        self.version = versiontag.text
William Benton a6a8fa9
        depTrees = project.findall(".//%sdependencies/%sdependency" % (namespace, namespace))
William Benton a6a8fa9
        alldeps = [Artifact.fromSubtree(depTree, namespace) for depTree in depTrees if interestingDep(depTree, namespace)]
William Benton a6a8fa9
        alldeps = [dep for dep in alldeps if not (dep.group == self.groupID and dep.artifact == self.artifactID)]
William Benton a6a8fa9
        self.deps = [dep for dep in alldeps if not dep.contains(self.ignored_deps)] + [Artifact.fromCoords(xtra) for xtra in self.extra_deps]
William Benton a6a8fa9
        jarmatch = re.match(".*JPP-(.*).pom", self.filename)
William Benton a6a8fa9
        self.jarname = (jarmatch and jarmatch.groups()[0] or None)
William Benton a6a8fa9
William Benton a6a8fa9
def cn_debug(*args):
William Benton a6a8fa9
    logging.getLogger("com.freevariable.climbing-nemesis").debug(*args)
William Benton a6a8fa9
William Benton a6a8fa9
def cn_info(*args):
William Benton a6a8fa9
    logging.getLogger("com.freevariable.climbing-nemesis").info(*args)
William Benton a6a8fa9
William Benton a6a8fa9
def resolveArtifact(group, artifact, pomfile=None, kind="jar", ignored_deps=[], override=None, extra_deps=[]):
William Benton a6a8fa9
    # XXX: some error checking would be the responsible thing to do here
William Benton a6a8fa9
    cn_debug("rA:  extra_deps is %r" % extra_deps)
William Benton a6a8fa9
    if pomfile is None:
William Benton a6a8fa9
        try:
William Benton a6a8fa9
            if getFedoraRelease() > 19:
William Benton a6a8fa9
                [pom] = subprocess.check_output(["xmvn-resolve", "%s:%s:pom:%s" % (group, artifact, kind)]).split()
William Benton a6a8fa9
            else:
William Benton a6a8fa9
                [pom] = subprocess.check_output(["xmvn-resolve", "%s:%s:%s" % (group, artifact, kind)]).split()
William Benton a6a8fa9
            return POM(pom, ignored_deps=ignored_deps, override=override, extra_deps=extra_deps)
William Benton a6a8fa9
        except:
William Benton a6a8fa9
            return DummyPOM(group, artifact)
William Benton a6a8fa9
    else:
William Benton a6a8fa9
        return POM(pomfile, ignored_deps=ignored_deps, override=override, extra_deps=extra_deps)
William Benton a6a8fa9
William Benton a6a8fa9
def resolveArtifacts(identifiers):
William Benton a6a8fa9
    coords = ["%s:%s:jar" % (group, artifact) for (group, artifact) in identifiers]
William Benton a6a8fa9
    poms =  subprocess.check_output(["xmvn-resolve"] + coords).split()
William Benton a6a8fa9
    return [POM(pom) for pom in poms]
William Benton a6a8fa9
William Benton a6a8fa9
def resolveJar(group, artifact):
William Benton a6a8fa9
    [jar] = subprocess.check_output(["xmvn-resolve", "%s:%s:jar:jar" % (group, artifact)]).split()
William Benton a6a8fa9
    return jar
William Benton a6a8fa9
William Benton a6a8fa9
def makeIvyXmlTree(org, module, revision, status="release", meta={}, deps=[]):
William Benton a6a8fa9
    ivy_module = ET.Element("ivy-module", {"version":"1.0", "xmlns:e":"http://ant.apache.org/ivy/extra"})
William Benton a6a8fa9
    info = ET.SubElement(ivy_module, "info", dict({"organisation":org, "module":module, "revision":revision, "status":status}.items() + meta.items()))
William Benton a6a8fa9
    info.text = " " # ensure a close tag
William Benton a6a8fa9
    confs = ET.SubElement(ivy_module, "configurations")
William Benton a6a8fa9
    for conf in ["default", "provided", "test"]:
William Benton a6a8fa9
        ET.SubElement(confs, "conf", {"name":conf})
William Benton a6a8fa9
    pubs = ET.SubElement(ivy_module, "publications")
William Benton a6a8fa9
    ET.SubElement(pubs, "artifact", {"name":module, "type":"jar"})
William Benton a6a8fa9
    if len(deps) > 0:
William Benton a6a8fa9
        deptree = ET.SubElement(ivy_module, "dependencies")
William Benton a6a8fa9
        for dep in deps:
William Benton a6a8fa9
            ET.SubElement(deptree, "dependency", {"org":dep.group, "name":dep.artifact, "rev":dep.version})
William Benton a6a8fa9
    return ET.ElementTree(ivy_module)
William Benton a6a8fa9
William Benton a6a8fa9
def writeIvyXml(org, module, revision, status="release", fileobj=None, meta={}, deps=[]):
William Benton a6a8fa9
    # XXX: handle deps!
William Benton a6a8fa9
    if fileobj is None:
William Benton a6a8fa9
        fileobj = StringIO.StringIO()
William Benton a6a8fa9
    tree = makeIvyXmlTree(org, module, revision, status, meta=meta, deps=deps)
William Benton a6a8fa9
    tree.write(fileobj, xml_declaration=True)
William Benton a6a8fa9
    return fileobj
William Benton a6a8fa9
William Benton a6a8fa9
def ivyXmlAsString(org, module, revision, status, meta={}, deps=[]):
William Benton a6a8fa9
    return writeIvyXml(org, module, revision, status, meta=meta, deps=deps).getvalue()
William Benton a6a8fa9
William Benton a6a8fa9
def placeArtifact(artifact_file, repo_dirname, org, module, revision, status="release", meta={}, deps=[], supplied_ivy_file=None, scala=None, override=None, override_dir_only=False):
William Benton a6a8fa9
    if scala is not None:
William Benton a6a8fa9
        module = module + "_%s" % scala
William Benton a6a8fa9
    jarmodule = module
William Benton a6a8fa9
    if override is not None:
William Benton a6a8fa9
        org, module = override
William Benton a6a8fa9
        if not override_dir_only:
William Benton a6a8fa9
            jarmodule = module
William Benton a6a8fa9
    repo_dir = realpath(repo_dirname)
William Benton a6a8fa9
    artifact_dir = pathjoin(*[repo_dir] + [org] + [module, revision])
William Benton a6a8fa9
    ivyxml_path = pathjoin(artifact_dir, "ivy.xml")
William Benton a6a8fa9
    artifact_repo_path = pathjoin(artifact_dir, "%s-%s.jar" % (jarmodule, revision))
William Benton a6a8fa9
    
William Benton a6a8fa9
    if not pathexists(artifact_dir):
William Benton a6a8fa9
        makedirs(artifact_dir)
William Benton a6a8fa9
    
William Benton a6a8fa9
    ivyxml_file = open(ivyxml_path, "w")
William Benton a6a8fa9
    if supplied_ivy_file is None:
William Benton a6a8fa9
        writeIvyXml(org, module, revision, status, ivyxml_file, meta=meta, deps=deps)
William Benton a6a8fa9
    else:
William Benton a6a8fa9
        copyfile(supplied_ivy_file, ivyxml_path)
William Benton a6a8fa9
    
William Benton a6a8fa9
    if pathexists(artifact_repo_path):
William Benton a6a8fa9
        rmfile(artifact_repo_path)
William Benton a6a8fa9
    
William Benton a6a8fa9
    symlink(artifact_file, artifact_repo_path)
William Benton a6a8fa9
William Benton a6a8fa9
def getFedoraRelease():
William Benton a6a8fa9
    cmd = "rpm -q --qf %{version} fedora-release"
William Benton a6a8fa9
    return int(subprocess.check_output(cmd.split()))
William Benton a6a8fa9
William Benton a6a8fa9
def main():
William Benton a6a8fa9
    parser = argparse.ArgumentParser(description="Place a locally-installed artifact in a custom local Ivy repository; get metadata from Maven")
William Benton a6a8fa9
    parser.add_argument("group", metavar="GROUP", type=str, help="name of group")
William Benton a6a8fa9
    parser.add_argument("artifact", metavar="ARTIFACT", type=str, help="name of artifact")
William Benton a6a8fa9
    parser.add_argument("repodir", metavar="REPO", type=str, help="location for local repo")
William Benton a6a8fa9
    parser.add_argument("--version", metavar="VERSION", type=str, help="version to advertise this artifact as, overriding Maven metadata")
William Benton a6a8fa9
    parser.add_argument("--meta", metavar="K=V", type=str, help="extra metadata to store in ivy.xml", action='append')
William Benton a6a8fa9
    parser.add_argument("--jarfile", metavar="JAR", type=str, help="local jar file (use instead of POM metadata")
William Benton a6a8fa9
    parser.add_argument("--pomfile", metavar="POM", type=str, help="local pom file (use instead of xmvn-resolved one")
William Benton a6a8fa9
    parser.add_argument("--log", metavar="LEVEL", type=str, help="logging level")
William Benton a6a8fa9
    parser.add_argument("--ivyfile", metavar="IVY", type=str, help="supplied Ivy file (use instead of POM metadata)")
William Benton a6a8fa9
    parser.add_argument("--scala", metavar="VERSION", type=str, help="encode given scala version in artifact name")
William Benton a6a8fa9
    parser.add_argument("--ignore", metavar="STR", type=str, help="ignore dependencies whose artifact or group contains str", action='append')
William Benton a6a8fa9
    parser.add_argument("--override", metavar="ORG:NAME", type=str, help="override organization and/or artifact name")
William Benton a6a8fa9
    parser.add_argument("--override-dir-only", action='store_true', help="override organization and/or artifact name")
William Benton a6a8fa9
    parser.add_argument("--extra-dep", metavar="ORG:NAME:VERSION", action='append', help="add the given dependencya")
William Benton a6a8fa9
    args = parser.parse_args()
William Benton a6a8fa9
    
William Benton a6a8fa9
    if args.log is not None:
William Benton a6a8fa9
        logging.basicConfig(level=getattr(logging, args.log.upper()))
William Benton a6a8fa9
    
William Benton a6a8fa9
    override = args.override and args.override.split(":") or None
William Benton a6a8fa9
    cn_debug("cl: args.extra_dep is %r" % args.extra_dep)
William Benton a6a8fa9
    extra_deps = args.extra_dep is not None and args.extra_dep or []
William Benton a6a8fa9
    
William Benton a6a8fa9
    pom = resolveArtifact(args.group, args.artifact, args.pomfile, "jar", ignored_deps=(args.ignore or []), override=((not args.override_dir_only) and override or None), extra_deps=extra_deps)
William Benton a6a8fa9
    
William Benton a6a8fa9
    if args.jarfile is None:
William Benton a6a8fa9
        jarfile = resolveJar(pom.groupID or args.group, pom.artifactID or args.artifact)
William Benton a6a8fa9
    else:
William Benton a6a8fa9
        jarfile = args.jarfile
William Benton a6a8fa9
    
William Benton a6a8fa9
    version = (args.version or pom.version)
William Benton a6a8fa9
    
William Benton a6a8fa9
    meta = dict([kv.split("=") for kv in (args.meta or [])])
William Benton a6a8fa9
    cn_debug("meta is %r" % meta)
William Benton a6a8fa9
    
William Benton a6a8fa9
    placeArtifact(jarfile, args.repodir, pom.groupID, pom.artifactID, version, meta=meta, deps=pom.deps, supplied_ivy_file=args.ivyfile, scala=args.scala, override=override, override_dir_only=args.override_dir_only)
William Benton a6a8fa9
William Benton a6a8fa9
if __name__ == "__main__":
William Benton a6a8fa9
    main()