Blame climbing-nemesis.py

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