Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
pantsbuild.pants / java / jar / jar_dependency.py
Size: Mime:
# Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import dataclasses
import os
from dataclasses import dataclass
from typing import Optional, Sequence, Tuple
from urllib import parse

from pants.base.build_environment import get_buildroot
from pants.base.hash_utils import stable_json_sha1
from pants.base.validation import assert_list
from pants.java.jar.exclude import Exclude
from pants.java.jar.jar_dependency_utils import M2Coordinate
from pants.util.memo import memoized_method, memoized_property
from pants.util.meta import frozen_after_init


class JarDependencyParseContextWrapper:
    """A pre-built Maven repository dependency.

    Examples:

      # The typical use case.
      jar('com.puppycrawl.tools', 'checkstyle', '1.2')

      # Test external dependency locally.
      jar('org.foobar', 'foobar', '1.2-SNAPSHOT',
          url='file:///Users/pantsdev/workspace/project/jars/checkstyle/checkstyle.jar')

      # Test external dependency locally using relative path (with respect to the path
      # of the belonging BUILD file)
      jar('org.foobar', 'foobar', '1.2-SNAPSHOT',
          url='file:../checkstyle/checkstyle.jar')
    """

    def __init__(self, parse_context):
        """
        :param parse_context: The BUILD file parse context.
        """
        self._parse_context = parse_context

    def __call__(
        self,
        org,
        name,
        rev=None,
        force=False,
        ext=None,
        url=None,
        apidocs=None,
        classifier=None,
        mutable=None,
        intransitive=False,
        excludes=None,
    ):
        """
        :param string org: The Maven ``groupId`` of this dependency.
        :param string name: The Maven ``artifactId`` of this dependency.
        :param string rev: The Maven ``version`` of this dependency.
          If unspecified the latest available version is used.
        :param boolean force: Force this specific artifact revision even if other transitive
          dependencies specify a different revision. This requires specifying the ``rev`` parameter.
        :param string ext: Extension of the artifact if different from the artifact type.
          This is sometimes needed for artifacts packaged with Maven bundle type but stored as jars.
        :param string url: URL of this artifact, if different from the Maven repo standard location
          (specifying this parameter is unusual). Path of file URL can be either absolute or relative
          to the belonging BUILD file.
        :param string apidocs: URL of existing javadocs, which if specified, pants-generated javadocs
          will properly hyperlink {\\ @link}s.
        :param string classifier: Classifier specifying the artifact variant to use.
        :param boolean mutable: Inhibit caching of this mutable artifact. A common use is for
          Maven -SNAPSHOT style artifacts in an active development/integration cycle.
        :param boolean intransitive: Declares this Dependency intransitive, indicating only the jar for
          the dependency itself should be downloaded and placed on the classpath
        :param list excludes: Transitive dependencies of this jar to exclude.
        :type excludes: list of :class:`pants.backend.jvm.targets.exclude.Exclude`
        """
        return JarDependency(
            org,
            name,
            rev,
            force,
            ext,
            url,
            apidocs,
            classifier,
            mutable,
            intransitive,
            excludes,
            self._parse_context.rel_path,
        )


@frozen_after_init
@dataclass(unsafe_hash=True)
class JarDependency:
    """A pre-built Maven repository dependency.

    This is the developer facing api, compared to the context wrapper class
    `JarDependencyParseContextWrapper`, which exposes api through build file to users.

    The only additional parameter `base_path` here is so that we can retrieve the file URL
    in its absolute (for ivy) or relative (for fingerprinting) form. The context wrapper class
    determines the `base_path` from where `jar` is defined at.

    If a relative file url is provided, its absolute form will be (`buildroot` + `base_path` + relative url).

    :API: public
    """

    org: str
    base_name: str
    rev: Optional[str]
    force: bool
    ext: Optional[str]
    url: Optional[str]
    apidocs: Optional[str]
    classifier: Optional[str]
    mutable: bool
    intransitive: bool
    excludes: Tuple[Exclude, ...]
    base_path: str

    def __init__(
        self,
        org: str,
        name: str,
        rev: Optional[str] = None,
        force: bool = False,
        ext: Optional[str] = None,
        url: Optional[str] = None,
        apidocs: Optional[str] = None,
        classifier: Optional[str] = None,
        mutable: bool = False,
        intransitive: bool = False,
        excludes: Optional[Sequence[Exclude]] = None,
        base_path: Optional[str] = None,
    ) -> None:
        self.org = org
        self.base_name = name
        self.rev = rev
        self.force = force
        self.ext = ext
        self.url = url
        self.apidocs = apidocs
        self.classifier = classifier
        self.mutable = mutable
        self.intransitive = intransitive
        self.excludes = JarDependency._prepare_excludes(excludes)
        base_path = base_path or "."
        if os.path.isabs(base_path):
            base_path = os.path.relpath(base_path, get_buildroot())
        self.base_path = base_path

    def __str__(self):
        return "JarDependency({})".format(self.coordinate)

    @staticmethod
    def _prepare_excludes(excludes):
        return tuple(
            assert_list(
                excludes,
                expected_type=Exclude,
                can_be_none=True,
                key_arg="excludes",
                allowable=(tuple, list,),
            )
        )

    @property
    def name(self):
        return self.base_name

    @memoized_method
    def get_url(self, relative=False):
        if self.url:
            parsed_url = parse.urlparse(self.url)
            if parsed_url.scheme == "file":
                if relative and os.path.isabs(parsed_url.path):
                    relative_path = os.path.relpath(
                        parsed_url.path, os.path.join(get_buildroot(), self.base_path)
                    )
                    return "file:{path}".format(path=os.path.normpath(relative_path))
                if not relative and not os.path.isabs(parsed_url.path):
                    abs_path = os.path.join(get_buildroot(), self.base_path, parsed_url.path)
                    return "file://{path}".format(path=os.path.normpath(abs_path))
        return self.url

    @property
    def transitive(self):
        return not self.intransitive

    def copy(self, **replacements):
        """Returns a clone of this JarDependency with the given replacements kwargs overlaid."""
        cls = type(self)
        kwargs = dataclasses.asdict(self)
        kwargs.update(replacements)
        org = kwargs.pop("org")
        base_name = kwargs.pop("base_name")
        # NB: This calls __init__() so will set things up properly for us, such as calling
        # _prepare_excludes.
        return cls(org, base_name, **kwargs)

    @memoized_property
    def coordinate(self):
        """Returns the maven coordinate of this jar.

        :rtype: :class:`pants.java.jar.M2Coordinate`
        """
        return M2Coordinate(
            org=self.org, name=self.name, rev=self.rev, classifier=self.classifier, ext=self.ext
        )

    def cache_key(self):
        excludes = [(e.org, e.name) for e in self.excludes]
        return stable_json_sha1(
            dict(
                org=self.org,
                name=self.name,
                rev=self.rev,
                force=self.force,
                ext=self.ext,
                url=self.get_url(relative=True),
                classifier=self.classifier,
                transitive=self.transitive,
                mutable=self.mutable,
                excludes=excludes,
            )
        )