Esempio n. 1
0
class InternalTarget(Target):
  """A baseclass for targets that support an optional dependency set."""

  class CycleException(TargetDefinitionException):
    """Thrown when a circular dependency is detected."""

    def __init__(self, cycle):
      Exception.__init__(self, 'Cycle detected:\n\t%s' % (
          ' ->\n\t'.join(str(target.address) for target in cycle)
      ))

  @classmethod
  def sort_targets(cls, internal_targets):
    """Returns a list of targets that internal_targets depend on sorted from most dependent to
    least.
    """

    roots = OrderedSet()
    inverted_deps = collections.defaultdict(OrderedSet)  # target -> dependent targets
    visited = set()
    path = OrderedSet()

    def invert(target):
      if target in path:
        path_list = list(path)
        cycle_head = path_list.index(target)
        cycle = path_list[cycle_head:] + [target]
        raise cls.CycleException(cycle)
      path.add(target)
      if target not in visited:
        visited.add(target)
        if getattr(target, 'internal_dependencies', None):
          for internal_dependency in target.internal_dependencies:
            if hasattr(internal_dependency, 'internal_dependencies'):
              inverted_deps[internal_dependency].add(target)
              invert(internal_dependency)
        else:
          roots.add(target)
      path.remove(target)

    for internal_target in internal_targets:
      invert(internal_target)

    ordered = []
    visited.clear()

    def topological_sort(target):
      if target not in visited:
        visited.add(target)
        if target in inverted_deps:
          for dep in inverted_deps[target]:
            topological_sort(dep)
        ordered.append(target)

    for root in roots:
      topological_sort(root)

    return ordered

  @classmethod
  def coalesce_targets(cls, internal_targets, discriminator):
    """Returns a list of targets internal_targets depend on sorted from most dependent to least and
    grouped where possible by target type as categorized by the given discriminator.
    """

    sorted_targets = InternalTarget.sort_targets(internal_targets)

    # can do no better for any of these:
    # []
    # [a]
    # [a,b]
    if len(sorted_targets) <= 2:
      return sorted_targets

    # For these, we'd like to coalesce if possible, like:
    # [a,b,a,c,a,c] -> [a,a,a,b,c,c]
    # adopt a quadratic worst case solution, when we find a type change edge, scan forward for
    # the opposite edge and then try to swap dependency pairs to move the type back left to its
    # grouping.  If the leftwards migration fails due to a dependency constraint, we just stop
    # and move on leaving "type islands".
    current_type = None

    # main scan left to right no backtracking
    for i in range(len(sorted_targets) - 1):
      current_target = sorted_targets[i]
      if current_type != discriminator(current_target):
        scanned_back = False

        # scan ahead for next type match
        for j in range(i + 1, len(sorted_targets)):
          look_ahead_target = sorted_targets[j]
          if current_type == discriminator(look_ahead_target):
            scanned_back = True

            # swap this guy as far back as we can
            for k in range(j, i, -1):
              previous_target = sorted_targets[k - 1]
              mismatching_types = current_type != discriminator(previous_target)
              not_a_dependency = look_ahead_target not in previous_target.internal_dependencies
              if mismatching_types and not_a_dependency:
                sorted_targets[k] = sorted_targets[k - 1]
                sorted_targets[k - 1] = look_ahead_target
              else:
                break # out of k

            break # out of j

        if not scanned_back: # done with coalescing the current type, move on to next
          current_type = discriminator(current_target)

    return sorted_targets

  def sort(self):
    """Returns a list of targets this target depends on sorted from most dependent to least."""

    return InternalTarget.sort_targets([self])

  def coalesce(self, discriminator):
    """Returns a list of targets this target depends on sorted from most dependent to least and
    grouped where possible by target type as categorized by the given discriminator.
    """

    return InternalTarget.coalesce_targets([self], discriminator)

  def __init__(self, name, dependencies, exclusives=None):
    Target.__init__(self, name, exclusives=exclusives)

    self._injected_deps = []
    self.processed_dependencies = resolve(dependencies)

    self.add_labels('internal')
    self.dependency_addresses = OrderedSet()
    self.dependencies = OrderedSet()
    self.internal_dependencies = OrderedSet()
    self.jar_dependencies = OrderedSet()

    # TODO(John Sirois): just use the more general check: if parsing: delay(doit) else: doit()
    # Fix how target _ids are built / addresses to not require a BUILD file - ie: support anonymous,
    # non-addressable targets - which is what meta-targets really are once created.

    # Defer dependency resolution after parsing the current BUILD file to allow for forward
    # references
    self._post_construct(self.update_dependencies, self.processed_dependencies)

    self._post_construct(self.inject_dependencies)

  def _propagate_exclusives(self):
    # Note: this overrides Target._propagate_exclusives without
    # calling the supermethod. Targets in pants do not necessarily
    # have a dependencies field, or ever have their dependencies
    # available at all pre-resolve. Subtypes of InternalTarget, however,
    # do have well-defined dependency lists in their dependencies field,
    # so we can do a better job propagating their exclusives quickly.
    if self.exclusives is not None:
      return
    self.exclusives = copy.deepcopy(self.declared_exclusives)
    for t in self.dependencies:
      if isinstance(t, Target):
        t._propagate_exclusives()
        self.add_to_exclusives(t.exclusives)
      elif hasattr(t, "declared_exclusives"):
        self.add_to_exclusives(t.declared_exclusives)


  def add_injected_dependency(self, spec):
    self._injected_deps.append(spec)

  def inject_dependencies(self):
    self.update_dependencies(resolve(self._injected_deps))

  def update_dependencies(self, dependencies):
    if dependencies:
      for dependency in dependencies:
        if hasattr(dependency, 'address'):
          self.dependency_addresses.add(dependency.address)
        for resolved_dependency in dependency.resolve():
          if is_concrete(resolved_dependency) and not self.valid_dependency(resolved_dependency):
            raise TargetDefinitionException(self, 'Cannot add %s as a dependency of %s'
                                                  % (resolved_dependency, self))
          self.dependencies.add(resolved_dependency)
          if isinstance(resolved_dependency, InternalTarget):
            self.internal_dependencies.add(resolved_dependency)
          if hasattr(resolved_dependency, '_as_jar_dependencies'):
            self.jar_dependencies.update(resolved_dependency._as_jar_dependencies())

  def valid_dependency(self, dep):
    """Subclasses can over-ride to reject invalid dependencies."""
    return True

  def replace_dependency(self, dependency, replacement):
    self.dependencies.discard(dependency)
    self.internal_dependencies.discard(dependency)
    self.jar_dependencies.discard(dependency)
    self.update_dependencies([replacement])

  def _walk(self, walked, work, predicate = None):
    Target._walk(self, walked, work, predicate)
    for dep in self.dependencies:
      if isinstance(dep, Target) and not dep in walked:
        walked.add(dep)
        if not predicate or predicate(dep):
          additional_targets = work(dep)
          dep._walk(walked, work, predicate)
          if additional_targets:
            for additional_target in additional_targets:
              additional_target._walk(walked, work, predicate)
Esempio n. 2
0
class Context(object):
    """Contains the context for a single run of pants.

  Goal implementations can access configuration data from pants.ini and any flags they have exposed
  here as well as information about the targets involved in the run.

  Advanced uses of the context include adding new targets to it for upstream or downstream goals to
  operate on and mapping of products a goal creates to the targets the products are associated with.
  """
    class Log(object):
        """A logger facade that logs into the pants reporting framework."""
        def __init__(self, run_tracker):
            self._run_tracker = run_tracker

        def debug(self, *msg_elements):
            self._run_tracker.log(Report.DEBUG, *msg_elements)

        def info(self, *msg_elements):
            self._run_tracker.log(Report.INFO, *msg_elements)

        def warn(self, *msg_elements):
            self._run_tracker.log(Report.WARN, *msg_elements)

        def error(self, *msg_elements):
            self._run_tracker.log(Report.ERROR, *msg_elements)

        def fatal(self, *msg_elements):
            self._run_tracker.log(Report.FATAL, *msg_elements)

    def __init__(self,
                 config,
                 options,
                 run_tracker,
                 target_roots,
                 requested_goals=None,
                 lock=Lock.unlocked(),
                 log=None,
                 target_base=None):
        self._config = config
        self._options = options
        self.run_tracker = run_tracker
        self._lock = lock
        self._log = log or Context.Log(run_tracker)
        self._target_base = target_base or Target
        self._state = {}
        self._products = Products()
        self._buildroot = get_buildroot()
        self._java_home = None  # Computed lazily.
        self.requested_goals = requested_goals or []

        self.replace_targets(target_roots)

    @property
    def config(self):
        """Returns a Config object containing the configuration data found in pants.ini."""
        return self._config

    @property
    def options(self):
        """Returns the command line options parsed at startup."""
        return self._options

    @property
    def lock(self):
        """Returns the global pants run lock so a goal can release it if needed."""
        return self._lock

    @property
    def log(self):
        """Returns the preferred logger for goals to use."""
        return self._log

    @property
    def products(self):
        """Returns the Products manager for the current run."""
        return self._products

    @property
    def target_roots(self):
        """Returns the targets specified on the command line.

    This set is strictly a subset of all targets in play for the run as returned by self.targets().
    Note that for a command line invocation that uses wildcard selectors : or ::, the targets
    globbed by the wildcards are considered to be target roots.
    """
        return self._target_roots

    @property
    def java_home(self):
        if self._java_home is None:
            self._java_home = os.path.realpath(
                os.path.dirname(find_java_home()))
        return self._java_home

    @property
    def ivy_home(self):
        return os.path.realpath(self.config.get('ivy', 'cache_dir'))

    def __str__(self):
        return 'Context(id:%s, state:%s, targets:%s)' % (self.id, self.state,
                                                         self.targets())

    def submit_foreground_work_and_wait(self, work, workunit_parent=None):
        """Returns the pool to which tasks can submit foreground (blocking) work."""
        return self.run_tracker.foreground_worker_pool().submit_work_and_wait(
            work, workunit_parent=workunit_parent)

    def submit_background_work_chain(self,
                                     work_chain,
                                     parent_workunit_name=None):
        background_root_workunit = self.run_tracker.get_background_root_workunit(
        )
        if parent_workunit_name:
            # We have to keep this workunit alive until all its child work is done, so
            # we manipulate the context manually instead of using it as a contextmanager.
            # This is slightly funky, but the with-context usage is so pervasive and
            # useful elsewhere that it's worth the funkiness in this one place.
            workunit_parent_ctx = self.run_tracker.new_workunit(
                name=parent_workunit_name,
                labels=[WorkUnit.MULTITOOL],
                parent=background_root_workunit)
            workunit_parent = workunit_parent_ctx.__enter__()
            done_hook = lambda: workunit_parent_ctx.__exit__(None, None, None)
        else:
            workunit_parent = background_root_workunit  # Run directly under the root.
            done_hook = None
        self.run_tracker.background_worker_pool().submit_async_work_chain(
            work_chain, workunit_parent=workunit_parent, done_hook=done_hook)

    def background_worker_pool(self):
        """Returns the pool to which tasks can submit background work."""
        return self.run_tracker.background_worker_pool()

    @contextmanager
    def new_workunit(self, name, labels=list(), cmd='', parent=None):
        with self.run_tracker.new_workunit(name=name,
                                           labels=labels,
                                           cmd=cmd,
                                           parent=parent) as workunit:
            yield workunit

    def acquire_lock(self):
        """ Acquire the global lock for the root directory associated with this context. When
    a goal requires serialization, it will call this to acquire the lock.
    """
        def onwait(pid):
            print('Waiting on pants process %s to complete' %
                  _process_info(pid),
                  file=sys.stderr)
            return True

        if self._lock.is_unlocked():
            runfile = os.path.join(self._buildroot, '.pants.run')
            self._lock = Lock.acquire(runfile, onwait=onwait)

    def release_lock(self):
        """Release the global lock if it's held.
    Returns True if the lock was held before this call.
    """
        if self._lock.is_unlocked():
            return False
        else:
            self._lock.release()
            self._lock = Lock.unlocked()
            return True

    def is_unlocked(self):
        """Whether the global lock object is actively holding the lock."""
        return self._lock.is_unlocked()

    def replace_targets(self, target_roots):
        """Replaces all targets in the context with the given roots and their transitive
    dependencies.
    """
        self._target_roots = list(target_roots)

        self._targets = OrderedSet()
        for target in self._target_roots:
            self.add_target(target)
        self.id = Target.identify(self._targets)

    def add_target(self, target):
        """Adds a target and its transitive dependencies to the run context.

    The target is not added to the target roots.
    """
        def add_targets(tgt):
            self._targets.update(tgt for tgt in tgt.resolve()
                                 if isinstance(tgt, self._target_base))

        target.walk(add_targets)

    def add_new_target(self, target_base, target_type, *args, **kwargs):
        """Creates a new target, adds it to the context and returns it.

    This method ensures the target resolves files against the given target_base, creating the
    directory if needed and registering a source root.
    """
        if 'derived_from' in kwargs:
            derived_from = kwargs.get('derived_from')
            del kwargs['derived_from']
        else:
            derived_from = None
        target = self._create_new_target(target_base, target_type, *args,
                                         **kwargs)
        self.add_target(target)
        if derived_from:
            target.derived_from = derived_from
        return target

    def _create_new_target(self, target_base, target_type, *args, **kwargs):
        if not os.path.exists(target_base):
            os.makedirs(target_base)
        SourceRoot.register(target_base, target_type)
        with ParseContext.temp(target_base):
            return target_type(*args, **kwargs)

    def remove_target(self, target):
        """Removes the given Target object from the context completely if present."""
        if target in self.target_roots:
            self.target_roots.remove(target)
        self._targets.discard(target)

    def targets(self, predicate=None):
        """Selects targets in-play in this run from the target roots and their transitive dependencies.

    If specified, the predicate will be used to narrow the scope of targets returned.
    """
        return filter(predicate, self._targets)

    def dependents(self, on_predicate=None, from_predicate=None):
        """Returns  a map from targets that satisfy the from_predicate to targets they depend on that
      satisfy the on_predicate.
    """
        core = set(self.targets(on_predicate))
        dependees = defaultdict(set)
        for target in self.targets(from_predicate):
            if hasattr(target, 'dependencies'):
                for dependency in target.dependencies:
                    if dependency in core:
                        dependees[target].add(dependency)
        return dependees

    def resolve(self, spec):
        """Returns an iterator over the target(s) the given address points to."""
        with ParseContext.temp():
            return Pants(spec).resolve()

    @contextmanager
    def state(self, key, default=None):
        value = self._state.get(key, default)
        yield value
        self._state[key] = value

    @contextmanager
    def timing(self, label):
        if self.timer:
            with self.timer.timing(label):
                yield
        else:
            yield
Esempio n. 3
0
class Context(object):
  """Contains the context for a single run of pants.

  Goal implementations can access configuration data from pants.ini and any flags they have exposed
  here as well as information about the targets involved in the run.

  Advanced uses of the context include adding new targets to it for upstream or downstream goals to
  operate on and mapping of products a goal creates to the targets the products are associated with.
  """

  class Log(object):
    def debug(self, msg): pass
    def info(self, msg): pass
    def warn(self, msg): pass

  def __init__(self, config, options, target_roots, lock=Lock.unlocked(), log=None):
    self._config = config
    self._options = options
    self._lock = lock
    self._log = log or Context.Log()
    self._state = {}
    self._products = Products()
    self._buildroot = get_buildroot()

    self.replace_targets(target_roots)

  @property
  def config(self):
    """Returns a Config object containing the configuration data found in pants.ini."""
    return self._config

  @property
  def options(self):
    """Returns the command line options parsed at startup."""
    return self._options

  @property
  def lock(self):
    """Returns the global pants run lock so a goal can release it if needed."""
    return self._lock

  @property
  def log(self):
    """Returns the preferred logger for goals to use."""
    return self._log

  @property
  def products(self):
    """Returns the Products manager for the current run."""
    return self._products

  @property
  def target_roots(self):
    """Returns the targets specified on the command line.

    This set is strictly a subset of all targets in play for the run as returned by self.targets().
    Note that for a command line invocation that uses wildcard selectors : or ::, the targets
    globbed by the wildcards are considered to be target roots.
    """
    return self._target_roots

  def __str__(self):
    return 'Context(id:%s, state:%s, targets:%s)' % (self.id, self.state, self.targets())

  def acquire_lock(self):
    """ Acquire the global lock for the root directory associated with this context. When
    a goal requires serialization, it will call this to acquire the lock.
    """
    def onwait(pid):
      print('Waiting on pants process %s to complete' % _process_info(pid), file=sys.stderr)
      return True
    if self._lock.is_unlocked():
      runfile = os.path.join(self._buildroot, '.pants.run')
      self._lock = Lock.acquire(runfile, onwait=onwait)

  def release_lock(self):
    """Release the global lock if it's held.
    Returns True if the lock was held before this call.
    """
    if self._lock is Lock.unlocked():
      return False
    else:
      self._lock.release()
      self._lock = Lock.unlocked()
      return True

  def replace_targets(self, target_roots):
    """Replaces all targets in the context with the given roots and their transitive
    dependencies.
    """
    self._target_roots = target_roots
    self._targets = OrderedSet()
    for target in target_roots:
      self.add_target(target)
    self.id = Target.identify(self._targets)

  def add_target(self, target):
    """Adds a target and its transitive dependencies to the run context.

    The target is not added to the target roots.
    """
    def add_targets(tgt):
      self._targets.update(tgt.resolve())
    target.walk(add_targets)

  def add_new_target(self, target_base, target_type, *args, **kwargs):
    """Creates a new target, adds it to the context and returns it.

    This method ensures the target resolves files against the given target_base, creating the
    directory if needed and registering a source root.
    """
    target = self._create_new_target(target_base, target_type, *args, **kwargs)
    self.add_target(target)
    return target

  def _create_new_target(self, target_base, target_type, *args, **kwargs):
    if not os.path.exists(target_base):
      os.makedirs(target_base)
    SourceRoot.register(target_base, target_type)
    with ParseContext.temp(target_base):
      return target_type(*args, **kwargs)

  def remove_target(self, target):
    """Removes the given Target object from the context completely if present."""
    if target in self.target_roots:
      self.target_roots.remove(target)
    self._targets.discard(target)

  def targets(self, predicate=None):
    """Selects targets in-play in this run from the target roots and their transitive dependencies.

    If specified, the predicate will be used to narrow the scope of targets returned.
    """
    return filter(predicate, self._targets)

  def dependants(self, on_predicate=None, from_predicate=None):
    """Returns  a map from targets that satisfy the from_predicate to targets they depend on that
      satisfy the on_predicate.
    """
    core = set(self.targets(on_predicate))
    dependees = defaultdict(set)
    for target in self.targets(from_predicate):
      if hasattr(target, 'dependencies'):
        for dependency in target.dependencies:
          if dependency in core:
            dependees[target].add(dependency)
    return dependees

  def resolve(self, spec):
    """Returns an iterator over the target(s) the given address points to."""
    with ParseContext.temp():
      return Pants(spec).resolve()

  @contextmanager
  def state(self, key, default=None):
    value = self._state.get(key, default)
    yield value
    self._state[key] = value
Esempio n. 4
0
class InternalTarget(Target):
  """A baseclass for targets that support an optional dependency set."""

  class CycleException(Exception):
    """Thrown when a circular dependency is detected."""

    def __init__(self, precedents, cycle):
      Exception.__init__(self, 'Cycle detected along path:\n\t%s' % (
        ' ->\n\t'.join(str(target.address) for target in list(precedents) + [ cycle ])
      ))

  @classmethod
  def check_cycles(cls, internal_target):
    """Validates the given InternalTarget has no circular dependencies.  Raises CycleException if
    it does."""

    dep_stack = OrderedSet()

    def descend(internal_dep):
      if internal_dep in dep_stack:
        raise InternalTarget.CycleException(dep_stack, internal_dep)
      if hasattr(internal_dep, 'internal_dependencies'):
        dep_stack.add(internal_dep)
        for dep in internal_dep.internal_dependencies:
          descend(dep)
        dep_stack.remove(internal_dep)

    descend(internal_target)

  @classmethod
  def sort_targets(cls, internal_targets):
    """Returns a list of targets that internal_targets depend on sorted from most dependent to
    least."""

    roots = OrderedSet()
    inverted_deps = collections.defaultdict(OrderedSet) # target -> dependent targets
    visited = set()

    def invert(target):
      if target not in visited:
        visited.add(target)
        if target.internal_dependencies:
          for internal_dependency in target.internal_dependencies:
            if isinstance(internal_dependency, InternalTarget):
              inverted_deps[internal_dependency].add(target)
              invert(internal_dependency)
        else:
          roots.add(target)

    for internal_target in internal_targets:
      invert(internal_target)

    sorted = []
    visited.clear()

    def topological_sort(target):
      if target not in visited:
        visited.add(target)
        if target in inverted_deps:
          for dep in inverted_deps[target]:
            topological_sort(dep)
        sorted.append(target)

    for root in roots:
      topological_sort(root)

    return sorted

  @classmethod
  def coalesce_targets(cls, internal_targets, discriminator):
    """Returns a list of targets internal_targets depend on sorted from most dependent to least and
    grouped where possible by target type as categorized by the given discriminator."""

    sorted_targets = InternalTarget.sort_targets(internal_targets)

    # can do no better for any of these:
    # []
    # [a]
    # [a,b]
    if len(sorted_targets) <= 2:
      return sorted_targets

    # For these, we'd like to coalesce if possible, like:
    # [a,b,a,c,a,c] -> [a,a,a,b,c,c]
    # adopt a quadratic worst case solution, when we find a type change edge, scan forward for
    # the opposite edge and then try to swap dependency pairs to move the type back left to its
    # grouping.  If the leftwards migration fails due to a dependency constraint, we just stop
    # and move on leaving "type islands".
    current_type = None

    # main scan left to right no backtracking
    for i in range(len(sorted_targets) - 1):
      current_target = sorted_targets[i]
      if current_type != discriminator(current_target):
        scanned_back = False

        # scan ahead for next type match
        for j in range(i + 1, len(sorted_targets)):
          look_ahead_target = sorted_targets[j]
          if current_type == discriminator(look_ahead_target):
            scanned_back = True

            # swap this guy as far back as we can
            for k in range(j, i, -1):
              previous_target = sorted_targets[k - 1]
              mismatching_types = current_type != discriminator(previous_target)
              not_a_dependency = look_ahead_target not in previous_target.internal_dependencies
              if mismatching_types and not_a_dependency:
                sorted_targets[k] = sorted_targets[k - 1]
                sorted_targets[k - 1] = look_ahead_target
              else:
                break # out of k

            break # out of j

        if not scanned_back: # done with coalescing the current type, move on to next
          current_type = discriminator(current_target)

    return sorted_targets

  def sort(self):
    """Returns a list of targets this target depends on sorted from most dependent to least."""

    return InternalTarget.sort_targets([ self ])

  def coalesce(self, discriminator):
    """Returns a list of targets this target depends on sorted from most dependent to least and
    grouped where possible by target type as categorized by the given discriminator."""

    return InternalTarget.coalesce_targets([ self ], discriminator)

  def __init__(self, name, dependencies, is_meta):
    Target.__init__(self, name, is_meta)

    self.add_label('internal')
    self.dependencies = OrderedSet()
    self.internal_dependencies = OrderedSet()
    self.jar_dependencies = OrderedSet()

    # TODO(John Sirois): if meta targets were truly built outside parse contexts - we could instead
    # just use the more general check: if parsing: delay(doit) else: doit()
    # Fix how target _ids are built / addresses to not require a BUILD file - ie: support anonymous,
    # non-addressable targets - which is what meta-targets really are once created.
    if is_meta:
      # Meta targets are built outside any parse context - so update dependencies immediately
      self.update_dependencies(dependencies)
    else:
      # Defer dependency resolution after parsing the current BUILD file to allow for forward
      # references
      self._post_construct(self.update_dependencies, dependencies)

  def update_dependencies(self, dependencies):
    if dependencies:
      for dependency in dependencies:
        for resolved_dependency in dependency.resolve():
          self.dependencies.add(resolved_dependency)
          if isinstance(resolved_dependency, InternalTarget):
            self.internal_dependencies.add(resolved_dependency)
          if hasattr(resolved_dependency, '_as_jar_dependencies'):
            self.jar_dependencies.update(resolved_dependency._as_jar_dependencies())

  def replace_dependency(self, dependency, replacement):
    self.dependencies.discard(dependency)
    self.internal_dependencies.discard(dependency)
    self.jar_dependencies.discard(dependency)
    self.update_dependencies([replacement])

  def _walk(self, walked, work, predicate = None):
    Target._walk(self, walked, work, predicate)
    for dep in self.dependencies:
      if isinstance(dep, Target) and not dep in walked:
        walked.add(dep)
        if not predicate or predicate(dep):
          additional_targets = work(dep)
          dep._walk(walked, work, predicate)
          if additional_targets:
            for additional_target in additional_targets:
              additional_target._walk(walked, work, predicate)
Esempio n. 5
0
class Context(object):
  """Contains the context for a single run of pants.

  Goal implementations can access configuration data from pants.ini and any flags they have exposed
  here as well as information about the targets involved in the run.

  Advanced uses of the context include adding new targets to it for upstream or downstream goals to
  operate on and mapping of products a goal creates to the targets the products are associated with.
  """

  class Log(object):
    """A logger facade that logs into the pants reporting framework."""
    def __init__(self, run_tracker):
      self._run_tracker = run_tracker

    def debug(self, *msg_elements): self._run_tracker.log(Report.DEBUG, *msg_elements)
    def info(self, *msg_elements): self._run_tracker.log(Report.INFO, *msg_elements)
    def warn(self, *msg_elements): self._run_tracker.log(Report.WARN, *msg_elements)
    def error(self, *msg_elements): self._run_tracker.log(Report.ERROR, *msg_elements)
    def fatal(self, *msg_elements): self._run_tracker.log(Report.FATAL, *msg_elements)

  def __init__(self, config, options, run_tracker, target_roots, requested_goals=None,
               lock=Lock.unlocked(), log=None, target_base=None):
    self._config = config
    self._options = options
    self.run_tracker = run_tracker
    self._lock = lock
    self._log = log or Context.Log(run_tracker)
    self._target_base = target_base or Target
    self._state = {}
    self._products = Products()
    self._buildroot = get_buildroot()
    self.requested_goals = requested_goals or []

    self.replace_targets(target_roots)

  @property
  def config(self):
    """Returns a Config object containing the configuration data found in pants.ini."""
    return self._config

  @property
  def options(self):
    """Returns the command line options parsed at startup."""
    return self._options

  @property
  def lock(self):
    """Returns the global pants run lock so a goal can release it if needed."""
    return self._lock

  @property
  def log(self):
    """Returns the preferred logger for goals to use."""
    return self._log

  @property
  def products(self):
    """Returns the Products manager for the current run."""
    return self._products

  @property
  def target_roots(self):
    """Returns the targets specified on the command line.

    This set is strictly a subset of all targets in play for the run as returned by self.targets().
    Note that for a command line invocation that uses wildcard selectors : or ::, the targets
    globbed by the wildcards are considered to be target roots.
    """
    return self._target_roots

  def __str__(self):
    return 'Context(id:%s, state:%s, targets:%s)' % (self.id, self.state, self.targets())

  def submit_foreground_work_and_wait(self, work, workunit_parent=None):
    """Returns the pool to which tasks can submit foreground (blocking) work."""
    return self.run_tracker.foreground_worker_pool().submit_work_and_wait(
      work, workunit_parent=workunit_parent)

  def submit_background_work_chain(self, work_chain, workunit_parent=None):
    self.run_tracker.background_worker_pool().submit_async_work_chain(
      work_chain, workunit_parent=workunit_parent)

  def background_worker_pool(self):
    """Returns the pool to which tasks can submit background work."""
    return self.run_tracker.background_worker_pool()

  @contextmanager
  def new_workunit(self, name, labels=list(), cmd='', parent=None):
    with self.run_tracker.new_workunit(name=name, labels=labels, cmd=cmd, parent=parent) as workunit:
      yield workunit

  def acquire_lock(self):
    """ Acquire the global lock for the root directory associated with this context. When
    a goal requires serialization, it will call this to acquire the lock.
    """
    def onwait(pid):
      print('Waiting on pants process %s to complete' % _process_info(pid), file=sys.stderr)
      return True
    if self._lock.is_unlocked():
      runfile = os.path.join(self._buildroot, '.pants.run')
      self._lock = Lock.acquire(runfile, onwait=onwait)

  def release_lock(self):
    """Release the global lock if it's held.
    Returns True if the lock was held before this call.
    """
    if self._lock.is_unlocked():
      return False
    else:
      self._lock.release()
      self._lock = Lock.unlocked()
      return True

  def is_unlocked(self):
    """Whether the global lock object is actively holding the lock."""
    return self._lock.is_unlocked()

  def replace_targets(self, target_roots):
    """Replaces all targets in the context with the given roots and their transitive
    dependencies.
    """
    self._target_roots = list(target_roots)

    self._targets = OrderedSet()
    for target in self._target_roots:
      self.add_target(target)
    self.id = Target.identify(self._targets)

  def add_target(self, target):
    """Adds a target and its transitive dependencies to the run context.

    The target is not added to the target roots.
    """
    def add_targets(tgt):
      self._targets.update(tgt for tgt in tgt.resolve() if isinstance(tgt, self._target_base))
    target.walk(add_targets)

  def add_new_target(self, target_base, target_type, *args, **kwargs):
    """Creates a new target, adds it to the context and returns it.

    This method ensures the target resolves files against the given target_base, creating the
    directory if needed and registering a source root.
    """
    if 'derived_from' in kwargs:
      derived_from = kwargs.get('derived_from')
      del kwargs['derived_from']
    else:
      derived_from = None
    target = self._create_new_target(target_base, target_type, *args, **kwargs)
    self.add_target(target)
    if derived_from:
      target.derived_from = derived_from
    return target

  def _create_new_target(self, target_base, target_type, *args, **kwargs):
    if not os.path.exists(target_base):
      os.makedirs(target_base)
    SourceRoot.register(target_base, target_type)
    with ParseContext.temp(target_base):
      return target_type(*args, **kwargs)

  def remove_target(self, target):
    """Removes the given Target object from the context completely if present."""
    if target in self.target_roots:
      self.target_roots.remove(target)
    self._targets.discard(target)

  def targets(self, predicate=None):
    """Selects targets in-play in this run from the target roots and their transitive dependencies.

    If specified, the predicate will be used to narrow the scope of targets returned.
    """
    return filter(predicate, self._targets)

  def dependents(self, on_predicate=None, from_predicate=None):
    """Returns  a map from targets that satisfy the from_predicate to targets they depend on that
      satisfy the on_predicate.
    """
    core = set(self.targets(on_predicate))
    dependees = defaultdict(set)
    for target in self.targets(from_predicate):
      if hasattr(target, 'dependencies'):
        for dependency in target.dependencies:
          if dependency in core:
            dependees[target].add(dependency)
    return dependees

  def resolve(self, spec):
    """Returns an iterator over the target(s) the given address points to."""
    with ParseContext.temp():
      return Pants(spec).resolve()

  @contextmanager
  def state(self, key, default=None):
    value = self._state.get(key, default)
    yield value
    self._state[key] = value

  @contextmanager
  def timing(self, label):
    if self.timer:
      with self.timer.timing(label):
        yield
    else:
      yield
Esempio n. 6
0
class Context(object):
  """Contains the context for a single run of pants.

  Goal implementations can access configuration data from pants.ini and any flags they have exposed
  here as well as information about the targets involved in the run.

  Advanced uses of the context include adding new targets to it for upstream or downstream goals to
  operate on and mapping of products a goal creates to the targets the products are associated with.
  """

  class Log(object):
    """A logger facade that logs into the pants reporting framework."""
    def __init__(self, run_tracker):
      self._run_tracker = run_tracker

    def debug(self, *msg_elements):
      self._run_tracker.log(Report.DEBUG, *msg_elements)

    def info(self, *msg_elements):
      self._run_tracker.log(Report.INFO, *msg_elements)

    def warn(self, *msg_elements):
      self._run_tracker.log(Report.WARN, *msg_elements)

    def error(self, *msg_elements):
      self._run_tracker.log(Report.ERROR, *msg_elements)

    def fatal(self, *msg_elements):
      self._run_tracker.log(Report.FATAL, *msg_elements)

  def __init__(self, config, options, run_tracker, target_roots, requested_goals=None,
               lock=None, log=None, target_base=None):
    self._config = config
    self._options = options
    self.run_tracker = run_tracker
    self._lock = lock or Lock.unlocked()
    self._log = log or Context.Log(run_tracker)
    self._target_base = target_base or Target

    self._state = {}
    self._products = Products()
    self._buildroot = get_buildroot()
    self._java_sysprops = None  # Computed lazily.
    self.requested_goals = requested_goals or []

    self.replace_targets(target_roots)

  @property
  def config(self):
    """Returns a Config object containing the configuration data found in pants.ini."""
    return self._config

  @property
  def options(self):
    """Returns the command line options parsed at startup."""
    return self._options

  @property
  def lock(self):
    """Returns the global pants run lock so a goal can release it if needed."""
    return self._lock

  @property
  def log(self):
    """Returns the preferred logger for goals to use."""
    return self._log

  @property
  def products(self):
    """Returns the Products manager for the current run."""
    return self._products

  @property
  def target_roots(self):
    """Returns the targets specified on the command line.

    This set is strictly a subset of all targets in play for the run as returned by self.targets().
    Note that for a command line invocation that uses wildcard selectors : or ::, the targets
    globbed by the wildcards are considered to be target roots.
    """
    return self._target_roots

  @property
  def java_sysprops(self):
    """The system properties of the JVM we use."""
    # TODO: In the future we can use these to hermeticize the Java enivronment rather than relying
    # on whatever's on the shell's PATH. E.g., you either specify a path to the Java home via a
    # cmd-line flag or .pantsrc, or we infer one from java.home but verify that the java.version
    # is a supported version.
    if self._java_sysprops is None:
      # TODO(John Sirois): Plumb a sane default distribution through 1 point of control
      self._java_sysprops = Distribution.cached().system_properties
    return self._java_sysprops

  @property
  def java_home(self):
    """Find the java home for the JVM we use."""
    # Implementation is a kind-of-insane hack: we run the jvm to get it to emit its
    # system properties. On some platforms there are so many hard and symbolic links into
    # the JRE dirs that it's actually quite hard to establish what path to use as the java home,
    # e.g., for the purpose of rebasing. In practice, this seems to work fine.
    # Note that for our purposes we take the parent of java.home.
    return os.path.realpath(os.path.dirname(self.java_sysprops['java.home']))

  @property
  def ivy_home(self):
    return os.path.realpath(self.config.get('ivy', 'cache_dir'))

  def __str__(self):
    return 'Context(id:%s, state:%s, targets:%s)' % (self.id, self.state, self.targets())

  def submit_foreground_work_and_wait(self, work, workunit_parent=None):
    """Returns the pool to which tasks can submit foreground (blocking) work."""
    return self.run_tracker.foreground_worker_pool().submit_work_and_wait(
      work, workunit_parent=workunit_parent)

  def submit_background_work_chain(self, work_chain, parent_workunit_name=None):
    background_root_workunit = self.run_tracker.get_background_root_workunit()
    if parent_workunit_name:
      # We have to keep this workunit alive until all its child work is done, so
      # we manipulate the context manually instead of using it as a contextmanager.
      # This is slightly funky, but the with-context usage is so pervasive and
      # useful elsewhere that it's worth the funkiness in this one place.
      workunit_parent_ctx = self.run_tracker.new_workunit_under_parent(
        name=parent_workunit_name, labels=[WorkUnit.MULTITOOL], parent=background_root_workunit)
      workunit_parent = workunit_parent_ctx.__enter__()
      done_hook = lambda: workunit_parent_ctx.__exit__(None, None, None)
    else:
      workunit_parent = background_root_workunit  # Run directly under the root.
      done_hook = None
    self.run_tracker.background_worker_pool().submit_async_work_chain(
      work_chain, workunit_parent=workunit_parent, done_hook=done_hook)

  def background_worker_pool(self):
    """Returns the pool to which tasks can submit background work."""
    return self.run_tracker.background_worker_pool()

  @contextmanager
  def new_workunit(self, name, labels=None, cmd=''):
    """Create a new workunit under the calling thread's current workunit."""
    with self.run_tracker.new_workunit(name=name, labels=labels, cmd=cmd) as workunit:
      yield workunit

  def acquire_lock(self):
    """ Acquire the global lock for the root directory associated with this context. When
    a goal requires serialization, it will call this to acquire the lock.
    """
    def onwait(pid):
      print('Waiting on pants process %s to complete' % _process_info(pid), file=sys.stderr)
      return True
    if self._lock.is_unlocked():
      runfile = os.path.join(self._buildroot, '.pants.run')
      self._lock = Lock.acquire(runfile, onwait=onwait)

  def release_lock(self):
    """Release the global lock if it's held.
    Returns True if the lock was held before this call.
    """
    if self._lock.is_unlocked():
      return False
    else:
      self._lock.release()
      self._lock = Lock.unlocked()
      return True

  def is_unlocked(self):
    """Whether the global lock object is actively holding the lock."""
    return self._lock.is_unlocked()

  def replace_targets(self, target_roots):
    """Replaces all targets in the context with the given roots and their transitive
    dependencies.
    """
    self._target_roots = list(target_roots)

    self._targets = OrderedSet()
    for target in self._target_roots:
      self.add_target(target)
    self.id = Target.identify(self._targets)

  def add_target(self, target):
    """Adds a target and its transitive dependencies to the run context.

    The target is not added to the target roots.
    """
    def add_targets(tgt):
      self._targets.update(tgt for tgt in tgt.resolve() if isinstance(tgt, self._target_base))
    target.walk(add_targets)

  def add_new_target(self, target_base, target_type, *args, **kwargs):
    """Creates a new target, adds it to the context and returns it.

    This method ensures the target resolves files against the given target_base, creating the
    directory if needed and registering a source root.
    """
    if 'derived_from' in kwargs:
      derived_from = kwargs.get('derived_from')
      del kwargs['derived_from']
    else:
      derived_from = None
    target = self._create_new_target(target_base, target_type, *args, **kwargs)
    self.add_target(target)
    if derived_from:
      target.derived_from = derived_from
    return target

  def _create_new_target(self, target_base, target_type, *args, **kwargs):
    if not os.path.exists(target_base):
      os.makedirs(target_base)
    SourceRoot.register(target_base, target_type)
    with ParseContext.temp(target_base):
      return target_type(*args, **kwargs)

  def remove_target(self, target):
    """Removes the given Target object from the context completely if present."""
    if target in self.target_roots:
      self.target_roots.remove(target)
    self._targets.discard(target)

  def targets(self, predicate=None):
    """Selects targets in-play in this run from the target roots and their transitive dependencies.

    If specified, the predicate will be used to narrow the scope of targets returned.
    """
    return filter(predicate, self._targets)

  def dependents(self, on_predicate=None, from_predicate=None):
    """Returns  a map from targets that satisfy the from_predicate to targets they depend on that
      satisfy the on_predicate.
    """
    core = set(self.targets(on_predicate))
    dependees = defaultdict(set)
    for target in self.targets(from_predicate):
      if hasattr(target, 'dependencies'):
        for dependency in target.dependencies:
          if dependency in core:
            dependees[target].add(dependency)
    return dependees

  def resolve(self, spec):
    """Returns an iterator over the target(s) the given address points to."""
    with ParseContext.temp():
      return Pants(spec).resolve()

  @contextmanager
  def state(self, key, default=None):
    value = self._state.get(key, default)
    yield value
    self._state[key] = value

  @contextmanager
  def timing(self, label):
    if self.timer:
      with self.timer.timing(label):
        yield
    else:
      yield
Esempio n. 7
0
class InternalTarget(Target):
    """A baseclass for targets that support an optional dependency set."""
    class CycleException(Exception):
        """Thrown when a circular dependency is detected."""
        def __init__(self, precedents, cycle):
            Exception.__init__(
                self, 'Cycle detected along path:\n\t%s' % (' ->\n\t'.join(
                    str(target.address)
                    for target in list(precedents) + [cycle])))

    @classmethod
    def check_cycles(cls, internal_target):
        """Validates the given InternalTarget has no circular dependencies.  Raises CycleException if
    it does."""

        dep_stack = OrderedSet()

        def descend(internal_dep):
            if internal_dep in dep_stack:
                raise InternalTarget.CycleException(dep_stack, internal_dep)
            if hasattr(internal_dep, 'internal_dependencies'):
                dep_stack.add(internal_dep)
                for dep in internal_dep.internal_dependencies:
                    descend(dep)
                dep_stack.remove(internal_dep)

        descend(internal_target)

    @classmethod
    def sort_targets(cls, internal_targets):
        """Returns a list of targets that internal_targets depend on sorted from most dependent to
    least."""

        roots = OrderedSet()
        inverted_deps = collections.defaultdict(
            OrderedSet)  # target -> dependent targets
        visited = set()

        def invert(target):
            if target not in visited:
                visited.add(target)
                if target.internal_dependencies:
                    for internal_dependency in target.internal_dependencies:
                        if isinstance(internal_dependency, InternalTarget):
                            inverted_deps[internal_dependency].add(target)
                            invert(internal_dependency)
                else:
                    roots.add(target)

        for internal_target in internal_targets:
            invert(internal_target)

        sorted = []
        visited.clear()

        def topological_sort(target):
            if target not in visited:
                visited.add(target)
                if target in inverted_deps:
                    for dep in inverted_deps[target]:
                        topological_sort(dep)
                sorted.append(target)

        for root in roots:
            topological_sort(root)

        return sorted

    @classmethod
    def coalesce_targets(cls, internal_targets, discriminator):
        """Returns a list of targets internal_targets depend on sorted from most dependent to least and
    grouped where possible by target type as categorized by the given discriminator."""

        sorted_targets = InternalTarget.sort_targets(internal_targets)

        # can do no better for any of these:
        # []
        # [a]
        # [a,b]
        if len(sorted_targets) <= 2:
            return sorted_targets

        # For these, we'd like to coalesce if possible, like:
        # [a,b,a,c,a,c] -> [a,a,a,b,c,c]
        # adopt a quadratic worst case solution, when we find a type change edge, scan forward for
        # the opposite edge and then try to swap dependency pairs to move the type back left to its
        # grouping.  If the leftwards migration fails due to a dependency constraint, we just stop
        # and move on leaving "type islands".
        current_type = None

        # main scan left to right no backtracking
        for i in range(len(sorted_targets) - 1):
            current_target = sorted_targets[i]
            if current_type != discriminator(current_target):
                scanned_back = False

                # scan ahead for next type match
                for j in range(i + 1, len(sorted_targets)):
                    look_ahead_target = sorted_targets[j]
                    if current_type == discriminator(look_ahead_target):
                        scanned_back = True

                        # swap this guy as far back as we can
                        for k in range(j, i, -1):
                            previous_target = sorted_targets[k - 1]
                            mismatching_types = current_type != discriminator(
                                previous_target)
                            not_a_dependency = look_ahead_target not in previous_target.internal_dependencies
                            if mismatching_types and not_a_dependency:
                                sorted_targets[k] = sorted_targets[k - 1]
                                sorted_targets[k - 1] = look_ahead_target
                            else:
                                break  # out of k

                        break  # out of j

                if not scanned_back:  # done with coalescing the current type, move on to next
                    current_type = discriminator(current_target)

        return sorted_targets

    def sort(self):
        """Returns a list of targets this target depends on sorted from most dependent to least."""

        return InternalTarget.sort_targets([self])

    def coalesce(self, discriminator):
        """Returns a list of targets this target depends on sorted from most dependent to least and
    grouped where possible by target type as categorized by the given discriminator."""

        return InternalTarget.coalesce_targets([self], discriminator)

    def __init__(self, name, dependencies, is_meta):
        Target.__init__(self, name, is_meta)

        self.dependencies = OrderedSet()
        self.internal_dependencies = OrderedSet()
        self.jar_dependencies = OrderedSet()

        # TODO(John Sirois): if meta targets were truly built outside parse contexts - we could instead
        # just use the more general check: if parsing: delay(doit) else: doit()
        # Fix how target _ids are built / addresses to not require a BUILD file - ie: support anonymous,
        # non-addressable targets - which is what meta-targets really are once created.
        if is_meta:
            # Meta targets are built outside any parse context - so update dependencies immediately
            self.update_dependencies(dependencies)
        else:
            # Defer dependency resolution after parsing the current BUILD file to allow for forward
            # references
            self._post_construct(self.update_dependencies, dependencies)

    def update_dependencies(self, dependencies):
        if dependencies:
            for dependency in dependencies:
                for resolved_dependency in dependency.resolve():
                    self.dependencies.add(resolved_dependency)
                    if isinstance(resolved_dependency, InternalTarget):
                        self.internal_dependencies.add(resolved_dependency)
                    if hasattr(resolved_dependency, '_as_jar_dependencies'):
                        self.jar_dependencies.update(
                            resolved_dependency._as_jar_dependencies())

    def replace_dependency(self, dependency, replacement):
        self.dependencies.discard(dependency)
        self.internal_dependencies.discard(dependency)
        self.jar_dependencies.discard(dependency)
        self.update_dependencies([replacement])

    def _walk(self, walked, work, predicate=None):
        Target._walk(self, walked, work, predicate)
        for dep in self.dependencies:
            if isinstance(dep, Target) and not dep in walked:
                walked.add(dep)
                if not predicate or predicate(dep):
                    additional_targets = work(dep)
                    dep._walk(walked, work, predicate)
                    if additional_targets:
                        for additional_target in additional_targets:
                            additional_target._walk(walked, work, predicate)
Esempio n. 8
0
class Context(object):
    """Contains the context for a single run of pants.

  Goal implementations can access configuration data from pants.ini and any flags they have exposed
  here as well as information about the targets involved in the run.

  Advanced uses of the context include adding new targets to it for upstream or downstream goals to
  operate on and mapping of products a goal creates to the targets the products are associated with.
  """
    class Log(object):
        def debug(self, msg):
            pass

        def info(self, msg):
            pass

        def warn(self, msg):
            pass

    def __init__(self, config, options, target_roots, lock=None, log=None):
        self._config = config
        self._options = options
        self._lock = lock or Lock.unlocked()
        self._log = log or Context.Log()
        self._state = {}
        self._products = Products()

        self.replace_targets(target_roots)

    @property
    def config(self):
        """Returns a Config object containing the configuration data found in pants.ini."""
        return self._config

    @property
    def options(self):
        """Returns the command line options parsed at startup."""
        return self._options

    @property
    def lock(self):
        """Returns the global pants run lock so a goal can release it if needed."""
        return self._lock

    @property
    def log(self):
        """Returns the preferred logger for goals to use."""
        return self._log

    @property
    def products(self):
        """Returns the Products manager for the current run."""
        return self._products

    @property
    def target_roots(self):
        """Returns the targets specified on the command line.

    This set is strictly a subset of all targets in play for the run as returned by self.targets().
    Note that for a command line invocation that uses wildcard selectors : or ::, the targets
    globbed by the wildcards are considered to be target roots.
    """
        return self._target_roots

    def __str__(self):
        return 'Context(id:%s, state:%s, targets:%s)' % (self.id, self.state,
                                                         self.targets())

    def replace_targets(self, target_roots):
        """Replaces all targets in the context with the given roots and their transitive
    dependencies.
    """
        self._target_roots = target_roots
        self._targets = OrderedSet()
        for target in target_roots:
            self.add_target(target)
        self.id = Target.identify(self._targets)

    def add_target(self, target):
        """Adds a target and its transitive dependencies to the run context.

    The target is not added to the target roots.
    """
        def add_targets(tgt):
            self._targets.update(tgt.resolve())

        target.walk(add_targets)

    def add_new_target(self, target_base, target_type, *args, **kwargs):
        """Creates a new target, adds it to the context and returns it.

    This method ensures the target resolves files against the given target_base, creating the
    directory if needed and registering a source root.
    """
        target = self._create_new_target(target_base, target_type, *args,
                                         **kwargs)
        self.add_target(target)
        return target

    def _create_new_target(self, target_base, target_type, *args, **kwargs):
        if not os.path.exists(target_base):
            os.makedirs(target_base)
        SourceRoot.register(target_base, target_type)
        with ParseContext.temp(target_base):
            return target_type(*args, **kwargs)

    def remove_target(self, target):
        """Removes the given Target object from the context completely if present."""
        if target in self.target_roots:
            self.target_roots.remove(target)
        self._targets.discard(target)

    def targets(self, predicate=None):
        """Selects targets in-play in this run from the target roots and their transitive dependencies.

    If specified, the predicate will be used to narrow the scope of targets returned.
    """
        return filter(predicate, self._targets)

    def dependants(self, on_predicate=None, from_predicate=None):
        """Returns  a map from targets that satisfy the from_predicate to targets they depend on that
      satisfy the on_predicate.
    """
        core = set(self.targets(on_predicate))
        dependees = defaultdict(set)
        for target in self.targets(from_predicate):
            if hasattr(target, 'dependencies'):
                for dependency in target.dependencies:
                    if dependency in core:
                        dependees[target].add(dependency)
        return dependees

    def resolve(self, spec):
        """Returns an iterator over the target(s) the given address points to."""
        with ParseContext.temp():
            return Pants(spec).resolve()

    @contextmanager
    def state(self, key, default=None):
        value = self._state.get(key, default)
        yield value
        self._state[key] = value