Mini Shell

Direktori : /lib/rpm/
Upload File :
Current File : //lib/rpm/maven.req

#!/usr/libexec/platform-python
# Copyright (c) 2014, Red Hat, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the
#    distribution.
# 3. Neither the name of Red Hat nor the names of its
#    contributors may be used to endorse or promote products derived
#    from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Authors:
#   Stanislav Ochotnicky <sochotnicky@redhat.com>
#   Michal Srb <msrb@redhat.com>

import os
import re
import sys
import traceback

from javapackages.common.config import get_config
from javapackages.common.util import (kill_parent_process,
                                      init_rpmgen,
                                      get_logger)
from javapackages.cache.metadata import MetadataCache
from javapackages.maven.pom import POM
from javapackages.maven.dependency import Dependency
from javapackages.metadata.metadata import Metadata
from javapackages.metadata.dependency import MetadataDependency
from javapackages.xmvn.xmvn_resolve import XMvnResolve, ResolutionRequest

_log = get_logger("maven.req")


class TagBuilder(object):
    def __init__(self, filelist=None):
        if filelist is None:
            filelist = sys.stdin

        paths = [line.rstrip() for line in filelist.readlines()]
        _log.info("input: {fl}".format(fl=paths))

        self.config = self._get_config()
        cache = MetadataCache(rpmconf)
        self.provided_artifacts = cache.get_provided_artifacts()
        self.skipped_artifacts = cache.get_skipped_artifacts()
        self.provided_osgi = cache.get_provided_osgi()
        self.metadata_dir = os.path.dirname(paths[0])

        for path in paths:
            metadata = cache.get_metadata_for_path(path)
            self.print_requires(metadata)

            # generate also R on plugins and extensions from POM file, but only
            # if this is POM-only package and xmvn-resolve is installed
            if XMvnResolve.is_available():
                if metadata.contains_only_poms():
                    self.print_pom_requires(metadata)

    def print_pom_requires(self, metadata):
        """
        Print Requires on plugins, extensions and parent POMs from POM files
        """

        unresolvable = []
        pom_requires = set()
        for artifact in metadata.artifacts:
            if artifact.path:
                pom = POM(artifact.get_buildroot_path())
                ppom = pom.parent

                deps = []
                reqs = []

                # parent POM
                if ppom:
                    if not (ppom.groupId and ppom.artifactId and ppom.version):
                        raise Exception("Not enough information to resolve "
                                        "parent POM for artifact {g}:{a},"
                                        .format(g=pom.groupId,
                                                a=pom.artifactId),
                                        "parent POM is: {pg}:{pa}:pom:{pv}"
                                        .format(pg=ppom.groupId,
                                                pa=ppom.artifactId,
                                                pv=ppom.version))
                    pom_dep = Dependency(ppom.groupId,
                                         ppom.artifactId,
                                         version=ppom.version,
                                         extension="pom")
                    deps.append(pom_dep)
                    pom_req = ResolutionRequest(ppom.groupId,
                                                ppom.artifactId,
                                                version=ppom.version,
                                                extension="pom")
                    reqs.append(pom_req)

                # plugins+extensions
                deps.extend(pom.plugins + pom.extensions)

                # create xmvn-resolve request for plugins and extensions, skip
                # parent POM, if there is any - we already have request created
                # for it
                for d in (deps[1:] if ppom else deps):
                    reqs.append(ResolutionRequest(d.groupId,
                                                  d.artifactId,
                                                  version=d.version))
                # TODO: check metadata first
                results = XMvnResolve.process_raw_request(reqs)

                for i, r in enumerate(results):
                    dep = MetadataDependency(deps[i].groupId,
                                             deps[i].artifactId,
                                             requestedVersion=deps[i].version,
                                             namespace=rpmconf.scl or "")
                    if isinstance(deps[i], Dependency):
                        dep.extension = deps[i].extension

                    # check if the dependency is provided by any of the
                    # subpackages
                    dver = dep.is_provided_by(self.provided_artifacts)[1]
                    if dver is not None:
                        # check if dependency is NOT provided by currently
                        # processed subpackage
                        if not dep.is_provided_by(metadata.artifacts)[0]:
                            req_str = dep.get_rpm_str(pkg_ver=dver,
                                                      namespace=artifact.namespace)
                            pom_requires.add(req_str)
                    elif r:
                        # it's an external dependency
                        if r.compatVersion != "SYSTEM":
                            dep.resolvedVersion = r.compatVersion
                        if r.namespace:
                            dep.namespace = r.namespace
                        pom_requires.add(dep.get_rpm_str())
                    else:
                        unresolvable.append(dep)
                        continue

        _log.info("from POM(s): {reqs}".format(reqs=", ".join(pom_requires)))
        if pom_requires:
            print("\n".join(pom_requires))

        if unresolvable:
            raise Exception("Unable to generate requires on unresolvable artifacts: {reqs}"
                            .format(reqs=", ".join([x.get_mvn_str() for x in unresolvable])))

    def print_requires(self, metadata):
        """Print Requires from given metadata file."""

        # dependencies most likely present in the buildroot (in the reactor),
        # but not marked for installation. Therefore requires cannot be
        # generated on them.
        skipped_but_required = []

        # dependencies on unresolvable artifacts
        unknown_deps = []

        # resulting requires
        requires = set()

        # always generate R on packages specified in config file
        always_req = self.config.get('always_generate', ())
        requires.update([x for x in always_req])

        # check if dependencies on POM files are satisfiable
        if XMvnResolve.is_available():
            unknown_deps = self._check_pom_deps(metadata)

        # process non-optional dependencies
        for dependency in list(set(x for x in metadata.get_required_artifacts()
                                   if not x.is_optional())):
            if dependency.is_skipped(self.skipped_artifacts):
                skipped_but_required.append(dependency)
                continue
            if dependency.resolvedVersion == "UNKNOWN":
                unknown_deps.append(dependency)
                continue

            self._generate_require(metadata, requires, dependency)

        if unknown_deps:
            self._handle_unknown_deps(unknown_deps)

        if skipped_but_required:
            self._handle_skipped_but_required_deps(skipped_but_required)

        requires.update(self._generate_java_requires(metadata))
        requires = self._filter_requires(requires)

        self._get_osgi_requires(metadata, requires)

        _log.info(", ".join(requires))

        # print resulting requires!
        print('\n'.join(requires))

    def _generate_require(self, metadata, requires, dependency):
        # check if dependency is provided by any metadata file in buildroot
        subpkg_dep, pkgver = dependency.is_provided_by(self.provided_artifacts)
        if subpkg_dep:
            # check if dependency isn't provided by same metadata file
            if not dependency.is_provided_by(metadata.artifacts)[0]:
                # requires on subpackages are always versioned
                rpmstr = dependency.get_rpm_str(namespace=dependency.namespace,
                                                compat_ver=dependency.resolvedVersion,
                                                pkg_ver=pkgver)
                requires.add(rpmstr)
        else:
            rpmstr = dependency.get_rpm_str(namespace=dependency.namespace,
                                            compat_ver=dependency.resolvedVersion)
            requires.add(rpmstr)


    def _get_osgi_requires(self, metadata, requires):
        """Read OSGi requires from metadata properties."""
        osgi_reqs = metadata.get_osgi_requires()
        osgi_provs = metadata.get_osgi_provides()
        for req in osgi_reqs:
            if req.bundle not in [x.bundle for x in self.provided_osgi]:
                requires.add(req.get_rpm_str())
            elif req.bundle not in [x.bundle for x in osgi_provs]:
                for prov in self.provided_osgi:
                    if prov.bundle == req.bundle:
                        requires.add(req.get_rpm_str(version=prov.version,
                                                     namespace=prov.namespace))
                        break

    def _handle_unknown_deps(self, unknown_deps):
        unknown_msg = "Following dependencies were not resolved and " \
                      "requires cannot be generated. Either remove the " \
                      "dependencies from pom.xml or add proper packages to " \
                      "BuildRequires:\n"
        for unknown in unknown_deps:
            required_by = self._find_what_requires(unknown)
            if required_by:
                unknown_msg += "{art} required by {req}\n".format(art=unknown,
                                                                  req=required_by)
            else:
                unknown_msg += "{art}\n".format(art=unknown)
        raise Exception(unknown_msg)

    def _handle_skipped_but_required_deps(self, skipped_but_required):
        skipped_msg = "Following artifacts were built, " \
                      "but are not being installed. However, other " \
                      "artifacts require them:\n"
        for skipped in skipped_but_required:
            required_by = self._find_what_requires(skipped)
            if required_by:
                skipped_msg += "{art} required by {req}\n".format(art=skipped,
                                                                  req=required_by)
            else:
                skipped_msg += "{art}\n".format(art=skipped)
        skipped_msg += "\nEither package these artifacts or do not "\
                       "build them. To package them call %mvn_package "\
                       "in %prep.\n"
        raise Exception(skipped_msg)

    def _generate_java_requires(self, metadata):
        requires = set()
        for rule_name, fn in (('java_requires', Metadata.get_java_requires),
                              ('java_devel_requires', Metadata.get_java_devel_requires)):
            rules = self.config.get(rule_name, {})
            if not rules.get('skip', False):
                req_versions = fn(metadata)
                if req_versions:
                    version_string = self._get_java_requires(req_versions)
                    parts = {'pkg': rules['package_name'],
                             'op': rules.get('cmp_operator', '>='),
                             'version': version_string}
                    requires.add("{pkg} {op} {version}".format(**parts))
                elif rules.get('always_generate'):
                    if rules.get('package_name'):
                        requires.add(rules.get('package_name'))
        return requires

    def _get_java_requires(self, reqs):
        major, minor = max([self._parse_java_requires(x) for x in reqs])
        if minor:
            return "1:{0}.{1}".format(major, minor)
        else:
            return "1:{0}".format(major)

    def _parse_java_requires(self, req):
        match = re.match(r'^(\d+)(?:\.(\d+))?$', req)
        if not match:
            raise ValueError("Unknown Java version {0}".format(req))

        major = int(match.group(1))
        minor = int(match.group(2) or 0)

        if not minor and major < 9:
            minor = major
            major = 1

        return major, minor

    def _filter_requires(self, requires):
        filters = self.config.get('requires_filter', [])
        filtered = set()
        regexes = []
        for filt in filters:
            artifact_glob = re.sub(r'\\\*', '.*', re.escape(filt))
            regexes.append(re.compile(r'^{0}(\s*[<>=].*)?$'.format(artifact_glob)))
            regexes.append(re.compile(r'mvn\({0}\)'.format(artifact_glob)))
        for req in requires:
            if not any([regex.search(req) for regex in regexes]):
                filtered.add(req)
        return filtered

    def _get_config(self):
        config = get_config()
        return config.get('maven.req', {}) if config else {}

    def _check_pom_deps(self, metadata):
        """ Check if dependencies on POM files are satisfiable """
        unresolvable = []
        requests = []
        deps = []
        for provided in metadata.get_provided_artifacts():
            for dep in provided.dependencies:
                if dep.extension != "pom":
                    continue
                if dep.is_provided_by(self.provided_artifacts)[0]:
                    continue
                else:
                    deps.append(dep)
                    request = ResolutionRequest(dep.groupId, dep.artifactId,
                                                extension=dep.extension,
                                                classifier=dep.classifier,
                                                version=dep.requestedVersion)
                    requests.append(request)
        if requests:
            results = XMvnResolve.process_raw_request(requests)
            for i, r in enumerate(results):
                if not r:
                    unresolvable.append(deps[i])
        return unresolvable

    def _find_what_requires(self, artifact):
        """
        Determine which artifact requires given artifact. This is an attempt to
        find the artifact by guessing where the pom might be and going through
        its dependencies. It is not meant to be reliable or pretty, just to
        print more descriptive error messages when we can. If it cannot find
        anything, returns None.
        """
        # TODO: simplify this
        try:
            pom_dir = os.path.join(self.metadata_dir, '..', 'maven-poms')
            for dirname, _, filenames in os.walk(pom_dir):
                for filename in filenames:
                    if filename.endswith(".pom"):
                        pomfile = os.path.join(dirname, filename)
                        pom = POM(pomfile)
                        dependencies = pom.dependencies
                        for dep in dependencies:
                            if dep.scope not in ('compile', 'runtime'):
                                if not (dep.scope == 'test' and pom.packaging == 'pom'):
                                    continue
                            if (artifact.artifactId == dep.artifactId and
                               artifact.groupId == dep.groupId and
                               artifact.extension == dep.extension and
                               artifact.classifier == dep.classifier):
                                return "{g}:{a}".format(g=pom.groupId,
                                                        a=pom.artifactId)
        except Exception:
            pass
        return None


if __name__ == "__main__":
    rpmconf = None
    try:
        rpmconf = init_rpmgen(sys.argv)
        builder = TagBuilder()
    except Exception:
        traceback.print_exc(file=sys.stderr)
        kill_parent_process(rpmconf)


# Local Variables:
# mode: Python
# End:
# vi: ft=python

Zerion Mini Shell 1.0