text: # coding=utf-8
  # Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
  # Licensed under the Apache License, Version 2.0 (see LICENSE).
  
  from __future__ import absolute_import, division, print_function, unicode_literals
  
  import os.path
  from builtins import object
  from contextlib import contextmanager
  
  from six import string_types
  from twitter.common.collections import maybe_list
  
  from pants.java import util
  from pants.java.distribution.distribution import DistributionLocator
  from pants.java.executor import Executor, SubprocessExecutor
  from pants.process.lock import OwnerPrintingInterProcessFileLock
  from pants.util.dirutil import safe_mkdir
  
  
  class Ivy(object):
    """Encapsulates the ivy cli taking care of the basic invocation letting you just worry about the
    args to pass to the cli itself.
  
    :API: public
    """
  
    class Error(Exception):
      """Indicates an error executing an ivy command."""
  
    def __init__(self, classpath, ivy_settings=None, ivy_resolution_cache_dir=None, extra_jvm_options=None):
      """Configures an ivy wrapper for the ivy distribution at the given classpath.
  
      :param ivy_settings: path to find settings.xml file
      :param ivy_resolution_cache_dir: path to store downloaded ivy artifacts
      :param extra_jvm_options: list of strings to add to command line when invoking Ivy
      """
      self._classpath = maybe_list(classpath)
      self._ivy_settings = ivy_settings
      if self._ivy_settings and not isinstance(self._ivy_settings, string_types):
        raise ValueError('ivy_settings must be a string, given {} of type {}'.format(
                           self._ivy_settings, type(self._ivy_settings)))
  
      self._ivy_resolution_cache_dir = ivy_resolution_cache_dir
      if not isinstance(self._ivy_resolution_cache_dir, string_types):
        raise ValueError('ivy_resolution_cache_dir must be a string, given {} of type {}'.format(
                           self._ivy_resolution_cache_dir, type(self._ivy_resolution_cache_dir)))
  
      self._extra_jvm_options = extra_jvm_options or []
      self._lock = OwnerPrintingInterProcessFileLock(
        os.path.join(self._ivy_resolution_cache_dir, 'pants_ivy.file_lock'))
  
    @property
    def ivy_settings(self):
      """Returns the ivysettings.xml path used by this `Ivy` instance.
  
      May be None if ivy's built in default ivysettings.xml of standard public resolvers is being
      used.
      """
      return self._ivy_settings
  
    @property
    def ivy_resolution_cache_dir(self):
      """Returns the ivy cache dir used by this `Ivy` instance."""
      return self._ivy_resolution_cache_dir
  
    @property
    @contextmanager
    def resolution_lock(self):
      safe_mkdir(self._ivy_resolution_cache_dir)
      with self._lock:
        yield
  
    def execute(self, jvm_options=None, args=None, executor=None,
                workunit_factory=None, workunit_name=None, workunit_labels=None):
      """Executes the ivy commandline client with the given args.
  
      Raises Ivy.Error if the command fails for any reason.
      :param executor: Java executor to run ivy with.
      """
      # NB(gmalmquist): It should be OK that we can't declare a subsystem_dependency in this file
      # (because it's just a plain old object), because Ivy is only constructed by Bootstrapper, which
      # makes an explicit call to IvySubsystem.global_instance() in its constructor, which in turn has
      # a declared dependency on DistributionLocator.
      executor = executor or SubprocessExecutor(DistributionLocator.cached())
      runner = self.runner(jvm_options=jvm_options, args=args, executor=executor)
      try:
        with self.resolution_lock:
          result = util.execute_runner(runner, workunit_factory, workunit_name, workunit_labels)
        if result != 0:
          raise self.Error('Ivy command failed with exit code {}{}'.format(
                             result, ': ' + ' '.join(args) if args else ''))
      except executor.Error as e:
        raise self.Error('Problem executing ivy: {}'.format(e))
  
    def runner(self, jvm_options=None, args=None, executor=None):
      """Creates an ivy commandline client runner for the given args."""
      args = args or []
      if self._ivy_settings and '-settings' not in args:
        args = ['-settings', self._ivy_settings] + args
  
      options = list(jvm_options) if jvm_options else []
      if self._ivy_resolution_cache_dir and '-cache' not in args:
        # TODO(John Sirois): Currently this is a magic property to support hand-crafted <caches/> in
        # ivysettings.xml.  Ideally we'd support either simple -caches or these hand-crafted cases
        # instead of just hand-crafted.  Clean this up by taking over ivysettings.xml and generating
        # it from BUILD constructs.
        options += ['-Divy.cache.dir={}'.format(self._ivy_resolution_cache_dir)]
      options += self._extra_jvm_options
  
      executor = executor or SubprocessExecutor(DistributionLocator.cached())
      if not isinstance(executor, Executor):
        raise ValueError('The executor argument must be an Executor instance, given {} of type {}'.format(
                           executor, type(executor)))
  
      return executor.runner(classpath=self._classpath, main='org.apache.ivy.Main',
                             jvm_options=options, args=args)
  