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