Exemplo n.º 1
0
    def __init__(self,
                 target,
                 root_dir,
                 extra_targets=None,
                 builder=None,
                 platforms=None,
                 interpreter=None,
                 conn_timeout=None):
        self._config = Config.load()
        self._target = target
        self._root = root_dir
        self._platforms = platforms
        self._interpreter = interpreter or PythonInterpreter.get()
        self._extra_targets = list(
            extra_targets) if extra_targets is not None else []
        self._builder = builder or PEXBuilder(tempfile.mkdtemp(),
                                              interpreter=self._interpreter)

        # Note: unrelated to the general pants artifact cache.
        self._egg_cache_root = os.path.join(
            PythonSetup(self._config).scratch_dir('artifact_cache',
                                                  default_name='artifacts'),
            str(self._interpreter.identity))

        self._key_generator = CacheKeyGenerator()
        self._build_invalidator = BuildInvalidator(self._egg_cache_root)
Exemplo n.º 2
0
    def __init__(self,
                 target,
                 root_dir,
                 extra_targets=None,
                 builder=None,
                 interpreter=None,
                 conn_timeout=None):
        self._config = Config.load()
        self._target = target
        self._root = root_dir
        self._interpreter = interpreter or PythonInterpreter.get()
        self._extra_targets = list(
            extra_targets) if extra_targets is not None else []
        self._resolver = MultiResolver(self._config,
                                       target,
                                       conn_timeout=conn_timeout)
        self._builder = builder or PEXBuilder(tempfile.mkdtemp(),
                                              interpreter=self._interpreter)

        # Note: unrelated to the general pants artifact cache.
        self._egg_cache_root = os.path.join(
            self._config.get('python-setup', 'artifact_cache'),
            str(self._interpreter.identity))

        self._key_generator = CacheKeyGenerator()
        self._build_invalidator = BuildInvalidator(self._egg_cache_root)
Exemplo n.º 3
0
  def __init__(self, cache_key_generator, build_invalidator_dir,
               invalidate_dependents, extra_data, only_externaldeps):
    self._cache_key_generator = cache_key_generator
    self._invalidate_dependents = invalidate_dependents
    self._extra_data = pickle.dumps(extra_data)  # extra_data may be None.
    self._sources = NO_SOURCES if only_externaldeps else TARGET_SOURCES

    self._invalidator = BuildInvalidator(build_invalidator_dir)
Exemplo n.º 4
0
  def __init__(self, target, root_dir, extra_targets=None, builder=None, interpreter=None,
      conn_timeout=None):
    self._config = Config.load()
    self._target = target
    self._root = root_dir
    self._interpreter = interpreter or PythonInterpreter.get()
    self._extra_targets = list(extra_targets) if extra_targets is not None else []
    self._resolver = MultiResolver(self._config, target, conn_timeout=conn_timeout)
    self._builder = builder or PEXBuilder(tempfile.mkdtemp(), interpreter=self._interpreter)

    # Note: unrelated to the general pants artifact cache.
    self._egg_cache_root = os.path.join(self._config.get('python-setup', 'artifact_cache'),
                                        str(self._interpreter.identity))

    self._key_generator = CacheKeyGenerator()
    self._build_invalidator = BuildInvalidator( self._egg_cache_root)
Exemplo n.º 5
0
    def __init__(
        self, target, root_dir, extra_targets=None, builder=None, platforms=None, interpreter=None, conn_timeout=None
    ):
        self._config = Config.load()
        self._target = target
        self._root = root_dir
        self._platforms = platforms
        self._interpreter = interpreter or PythonInterpreter.get()
        self._extra_targets = list(extra_targets) if extra_targets is not None else []
        self._builder = builder or PEXBuilder(tempfile.mkdtemp(), interpreter=self._interpreter)

        # Note: unrelated to the general pants artifact cache.
        self._egg_cache_root = os.path.join(
            PythonSetup(self._config).scratch_dir("artifact_cache", default_name="artifacts"),
            str(self._interpreter.identity),
        )

        self._key_generator = CacheKeyGenerator()
        self._build_invalidator = BuildInvalidator(self._egg_cache_root)
Exemplo n.º 6
0
class CacheManager(object):
  """Manages cache checks, updates and invalidation keeping track of basic change
  and invalidation statistics.
  Note that this is distinct from the ArtifactCache concept, and should probably be renamed.
  """
  def __init__(self, cache_key_generator, build_invalidator_dir,
               invalidate_dependents, extra_data, only_externaldeps):
    self._cache_key_generator = cache_key_generator
    self._invalidate_dependents = invalidate_dependents
    self._extra_data = pickle.dumps(extra_data)  # extra_data may be None.
    self._sources = NO_SOURCES if only_externaldeps else TARGET_SOURCES

    self._invalidator = BuildInvalidator(build_invalidator_dir)

  def update(self, vts):
    """Mark a changed or invalidated VersionedTargetSet as successfully processed."""
    for vt in vts.versioned_targets:
      self._invalidator.update(vt.cache_key)
      vt.valid = True
    self._invalidator.update(vts.cache_key)
    vts.valid = True

  def force_invalidate(self, vts):
    """Force invalidation of a VersionedTargetSet."""
    for vt in vts.versioned_targets:
      self._invalidator.force_invalidate(vt.cache_key)
      vt.valid = False
    self._invalidator.force_invalidate(vts.cache_key)
    vts.valid = False

  def check(self, targets, partition_size_hint=None):
    """Checks whether each of the targets has changed and invalidates it if so.

    Returns a list of VersionedTargetSet objects (either valid or invalid). The returned sets
    'cover' the input targets, possibly partitioning them, and are in topological order.
    The caller can inspect these in order and, e.g., rebuild the invalid ones.
    """
    all_vts = self._sort_and_validate_targets(targets)
    invalid_vts = filter(lambda vt: not vt.valid, all_vts)
    return InvalidationCheck(all_vts, invalid_vts, partition_size_hint)

  def _sort_and_validate_targets(self, targets):
    """Validate each target.

    Returns a topologically ordered set of VersionedTargets, each representing one input target.
    """
    # We must check the targets in this order, to ensure correctness if invalidate_dependents=True,
    # since we use earlier cache keys to compute later cache keys in this case.
    ordered_targets = self._order_target_list(targets)

    # This will be a list of VersionedTargets that correspond to @targets.
    versioned_targets = []

    # This will be a mapping from each target to its corresponding VersionedTarget.
    versioned_targets_by_target = {}

    # Map from id to current fingerprint of the target with that id. We update this as we iterate,
    # in topological order, so when handling a target, this will already contain all its deps (in
    # this round).
    id_to_hash = {}

    for target in ordered_targets:
      dependency_keys = set()
      if self._invalidate_dependents and hasattr(target, 'dependencies'):
        # Note that we only need to do this for the immediate deps, because those will already
        # reflect changes in their own deps.
        for dep in target.dependencies:
          # We rely on the fact that any deps have already been processed, either in an earlier
          # round or because they came first in ordered_targets.
          # Note that only external deps (e.g., JarDependency) or targets with sources can
          # affect invalidation. Other targets (JarLibrary, Pants) are just dependency scaffolding.
          if isinstance(dep, ExternalDependency):
            dependency_keys.add(dep.cache_key())
          elif isinstance(dep, TargetWithSources):
            fprint = id_to_hash.get(dep.id, None)
            if fprint is None:
              # It may have been processed in a prior round, and therefore the fprint should
              # have been written out by the invalidator.
              fprint = self._invalidator.existing_hash(dep.id)
              # Note that fprint may still be None here. E.g., a codegen target is in the list
              # of deps, but its fprint is not visible to our self._invalidator (that of the
              # target synthesized from it is visible, so invalidation will still be correct.)
              #
              # Another case where this can happen is a dep of a codegen target on, say,
              # a java target that hasn't been built yet (again, the synthesized target will
              # depend on that same java target, so invalidation will still be correct.)
              # TODO(benjy): Make this simpler and more obviously correct.
            if fprint is not None:
              dependency_keys.add(fprint)
          elif isinstance(dep, JarLibrary) or isinstance(dep, Pants):
            pass
          else:
            raise ValueError('Cannot calculate a cache_key for a dependency: %s' % dep)
      cache_key = self._key_for(target, dependency_keys)
      id_to_hash[target.id] = cache_key.hash

      # Create a VersionedTarget corresponding to @target.
      versioned_target = VersionedTarget(self, target, cache_key)

      # Add the new VersionedTarget to the list of computed VersionedTargets.
      versioned_targets.append(versioned_target)

      # Add to the mapping from Targets to VersionedTargets, for use in hooking up VersionedTarget
      # dependencies below.
      versioned_targets_by_target[target] = versioned_target

    # Having created all applicable VersionedTargets, now we build the VersionedTarget dependency
    # graph, looking through targets that don't correspond to VersionedTargets themselves.
    versioned_target_deps_by_target = {}

    def get_versioned_target_deps_for_target(target):
      # For every dependency of @target, we will store its corresponding VersionedTarget here. For
      # dependencies that don't correspond to a VersionedTarget (e.g. pass-through dependency
      # wrappers), we will resolve their actual dependencies and find VersionedTargets for them.
      versioned_target_deps = set([])
      if hasattr(target, 'dependencies'):
        for dep in target.dependencies:
          for dependency in dep.resolve():
            if dependency in versioned_targets_by_target:
              # If there exists a VersionedTarget corresponding to this Target, store it and
              # continue.
              versioned_target_deps.add(versioned_targets_by_target[dependency])
            elif dependency in versioned_target_deps_by_target:
              # Otherwise, see if we've already resolved this dependency to the VersionedTargets it
              # depends on, and use those.
              versioned_target_deps.update(versioned_target_deps_by_target[dependency])
            else:
              # Otherwise, compute the VersionedTargets that correspond to this dependency's
              # dependencies, cache and use the computed result.
              versioned_target_deps_by_target[dependency] = get_versioned_target_deps_for_target(
                  dependency)
              versioned_target_deps.update(versioned_target_deps_by_target[dependency])

      # Return the VersionedTarget dependencies that this target's VersionedTarget should depend on.
      return versioned_target_deps

    # Initialize all VersionedTargets to point to the VersionedTargets they depend on.
    for versioned_target in versioned_targets:
      versioned_target.dependencies = get_versioned_target_deps_for_target(versioned_target.target)

    return versioned_targets

  def needs_update(self, cache_key):
    return self._invalidator.needs_update(cache_key)

  def _order_target_list(self, targets):
    """Orders the targets topologically, from least to most dependent."""
    targets = set(t for t in targets if isinstance(t, Target))
    return filter(targets.__contains__, reversed(InternalTarget.sort_targets(targets)))

  def _key_for(self, target, dependency_keys):
    def fingerprint_extra(sha):
      sha.update(self._extra_data)
      for key in sorted(dependency_keys):  # Sort to ensure hashing in a consistent order.
        sha.update(key)

    return self._cache_key_generator.key_for_target(
      target,
      sources=self._sources,
      fingerprint_extra=fingerprint_extra
    )
Exemplo n.º 7
0
 def invalidate(self):
     """Invalidates all targets for this task."""
     BuildInvalidator(self._build_invalidator_dir).force_invalidate_all()
Exemplo n.º 8
0
class CacheManager(object):
    """Manages cache checks, updates and invalidation keeping track of basic change
  and invalidation statistics.
  """
    def __init__(self, cache_key_generator, build_invalidator_dir,
                 invalidate_dependents, extra_data, only_externaldeps):
        self._cache_key_generator = cache_key_generator
        self._invalidate_dependents = invalidate_dependents
        self._extra_data = pickle.dumps(extra_data)  # extra_data may be None.
        self._sources = NO_SOURCES if only_externaldeps else TARGET_SOURCES

        self._invalidator = BuildInvalidator(build_invalidator_dir)

    def update(self, vts):
        """Mark a changed or invalidated VersionedTargetSet as successfully processed."""
        for cache_key in vts.per_target_cache_keys:
            self._invalidator.update(cache_key)
        self._invalidator.update(vts.cache_key)

    def check(self, targets, partition_size_hint):
        """Checks whether each of the targets has changed and invalidates it if so.

    Returns a list of VersionedTargetSet objects (either valid or invalid). The returned sets 'cover'
    the input targets, possibly partitioning them, and are in topological order.
    The caller can inspect these in order and, e.g., rebuild the invalid ones.
    """
        all_vts = self._validate(targets)
        invalid_vts = filter(lambda vt: not vt.valid, all_vts)
        all_vts_partitioned = self._partition_versioned_targets(
            all_vts, partition_size_hint)
        invalid_vts_partitioned = self._partition_versioned_targets(
            invalid_vts, partition_size_hint)
        return InvalidationCheck(all_vts, all_vts_partitioned, invalid_vts,
                                 invalid_vts_partitioned)

    def _validate(self, targets):
        """Validate each target.

    Returns a topologically ordered set of VersionedTargets, each representing one input target.
    """
        # We must check the targets in this order, to ensure correctness if invalidate_dependents=True, since
        # we use earlier cache keys to compute later cache keys in this case.
        ordered_targets = self._order_target_list(targets)
        versioned_targets = []

        # Map from id to current fingerprint of the target with that id. We update this as we iterate, in
        # topological order, so when handling a target, this will already contain all its deps (in this round).
        id_to_hash = {}

        for target in ordered_targets:
            dependency_keys = set()
            if self._invalidate_dependents and hasattr(target, 'dependencies'):
                # Note that we only need to do this for the immediate deps, because those will already
                # reflect changes in their own deps.
                for dep in target.dependencies:
                    # We rely on the fact that any deps have already been processed, either in an earlier round or
                    # because they came first in ordered_targets.
                    if isinstance(dep, Target):
                        hash = id_to_hash.get(dep.id, None)
                        if hash is None:
                            # It may have been processed in a prior round, and therefore the hash should
                            # have been written out by the invalidator.
                            hash = self._invalidator.existing_hash(dep.id)
                            # Note that hash may be None here, indicating that the dependency will not be processed
                            # until a later phase. For example, if a codegen target depends on a library target (because
                            # the generated code needs that library).
                        if hash is not None:
                            dependency_keys.add(hash)
                    elif isinstance(dep, JarDependency):
                        jarid = ''
                        for key in CacheManager._JAR_HASH_KEYS:
                            jarid += str(getattr(dep, key))
                        dependency_keys.add(jarid)
                    else:
                        dependency_keys.add(str(dep))
                        # TODO(John Sirois): Hashing on external targets should have a formal api - we happen to
                        # know jars are special and python requirements __str__ works for this purpose.
            cache_key = self._key_for(target, dependency_keys)
            id_to_hash[target.id] = cache_key.hash
            versioned_targets.append(
                VersionedTargetSet(self, [target], [cache_key]))

        return versioned_targets

    def needs_update(self, cache_key):
        return self._invalidator.needs_update(cache_key)

    def _order_target_list(self, targets):
        """Orders the targets topologically, from least to most dependent."""
        target_ids = set([x.id for x in targets])

        # Most to least dependent.
        reverse_ordered_targets_and_deps = InternalTarget.sort_targets(targets)
        # Least to most dependent. We must build in this order.
        ordered_targets_and_deps = reversed(reverse_ordered_targets_and_deps)
        # Return just the ones that were originally in targets.
        return filter(lambda x: x.id in target_ids, ordered_targets_and_deps)

    def _partition_versioned_targets(self, versioned_targets,
                                     partition_size_hint):
        """Groups versioned targets so that each group has roughly the same number of sources.

    versioned_targets is a list of VersionedTargetSet objects  [ vt1, vt2, vt3, vt4, vt5, vt6, ...].

    Returns a list of VersionedTargetSet objects, e.g., [ VT1, VT2, VT3, ...] representing the
    same underlying targets. E.g., VT1 is the combination of [vt1, vt2, vt3], VT2 is the combination
    of [vt4, vt5] and VT3 is [vt6].

    The new versioned targets are chosen to have roughly partition_size_hint sources.

    This is useful as a compromise between flat mode, where we build all targets in a
    single compiler invocation, and non-flat mode, where we invoke a compiler for each target,
    which may lead to lots of compiler startup overhead. A task can choose instead to build one
    group at a time.
    """
        res = []

        # Hack around the python outer scope problem.
        class VtGroup(object):
            def __init__(self):
                self.vts = []
                self.total_sources = 0

        current_group = VtGroup()

        def add_to_current_group(vt):
            current_group.vts.append(vt)
            current_group.total_sources += vt.cache_key.num_sources

        def close_current_group():
            if len(current_group.vts) > 0:
                new_vt = self._combine_versioned_targets(current_group.vts)
                res.append(new_vt)
                current_group.vts = []
                current_group.total_sources = 0

        for vt in versioned_targets:
            add_to_current_group(vt)
            if current_group.total_sources > partition_size_hint:
                if current_group.total_sources > 1.5 * partition_size_hint and len(
                        current_group.vts) > 1:
                    # Too big. Close the current group without this vt and add it to the next one.
                    current_group.vts.pop()
                    close_current_group()
                    add_to_current_group(vt)
                else:
                    close_current_group()
        close_current_group()  # Close the last group, if any.

        return res

    def _combine_versioned_targets(self, vts):
        targets = []
        for vt in vts:
            targets.extend(vt.targets)
        return VersionedTargetSet(self, targets, [vt.cache_key for vt in vts])

    def _key_for(self, target, dependency_keys):
        def fingerprint_extra(sha):
            sha.update(self._extra_data)
            for key in dependency_keys:
                sha.update(key)

        return self._cache_key_generator.key_for_target(
            target, sources=self._sources, fingerprint_extra=fingerprint_extra)

    _JAR_HASH_KEYS = ('org', 'name', 'rev', 'force', 'excludes', 'transitive',
                      '_configurations', 'artifacts')
Exemplo n.º 9
0
class CacheManager(object):
    """Manages cache checks, updates and invalidation keeping track of basic change
  and invalidation statistics.
  Note that this is distinct from the ArtifactCache concept, and should probably be renamed.
  """
    def __init__(self, cache_key_generator, build_invalidator_dir,
                 invalidate_dependents, extra_data, only_externaldeps):
        self._cache_key_generator = cache_key_generator
        self._invalidate_dependents = invalidate_dependents
        self._extra_data = pickle.dumps(extra_data)  # extra_data may be None.
        self._sources = NO_SOURCES if only_externaldeps else TARGET_SOURCES

        self._invalidator = BuildInvalidator(build_invalidator_dir)

    def update(self, vts):
        """Mark a changed or invalidated VersionedTargetSet as successfully processed."""
        for vt in vts.versioned_targets:
            self._invalidator.update(vt.cache_key)
            vt.valid = True
        self._invalidator.update(vts.cache_key)
        vts.valid = True

    def force_invalidate(self, vts):
        """Force invalidation of a VersionedTargetSet."""
        for vt in vts.versioned_targets:
            self._invalidator.force_invalidate(vt.cache_key)
            vt.valid = False
        self._invalidator.force_invalidate(vts.cache_key)
        vts.valid = False

    def check(self, targets, partition_size_hint=None):
        """Checks whether each of the targets has changed and invalidates it if so.

    Returns a list of VersionedTargetSet objects (either valid or invalid). The returned sets 'cover'
    the input targets, possibly partitioning them, and are in topological order.
    The caller can inspect these in order and, e.g., rebuild the invalid ones.
    """
        all_vts = self._sort_and_validate_targets(targets)
        invalid_vts = filter(lambda vt: not vt.valid, all_vts)
        return InvalidationCheck(all_vts, invalid_vts, partition_size_hint)

    def _sort_and_validate_targets(self, targets):
        """Validate each target.

    Returns a topologically ordered set of VersionedTargets, each representing one input target.
    """
        # We must check the targets in this order, to ensure correctness if invalidate_dependents=True, since
        # we use earlier cache keys to compute later cache keys in this case.
        ordered_targets = self._order_target_list(targets)

        # This will be a list of VersionedTargets that correspond to @targets.
        versioned_targets = []

        # This will be a mapping from each target to its corresponding VersionedTarget.
        versioned_targets_by_target = {}

        # Map from id to current fingerprint of the target with that id. We update this as we iterate, in
        # topological order, so when handling a target, this will already contain all its deps (in this round).
        id_to_hash = {}

        for target in ordered_targets:
            dependency_keys = set()
            if self._invalidate_dependents and hasattr(target, 'dependencies'):
                # Note that we only need to do this for the immediate deps, because those will already
                # reflect changes in their own deps.
                for dep in target.dependencies:
                    # We rely on the fact that any deps have already been processed, either in an earlier round or
                    # because they came first in ordered_targets.
                    if isinstance(dep, Target):
                        hash = id_to_hash.get(dep.id, None)
                        if hash is None:
                            # It may have been processed in a prior round, and therefore the hash should
                            # have been written out by the invalidator.
                            hash = self._invalidator.existing_hash(dep.id)
                            # Note that hash may be None here, indicating that the dependency will not be processed
                            # until a later phase. For example, if a codegen target depends on a library target (because
                            # the generated code needs that library).
                        if hash is not None:
                            dependency_keys.add(hash)
                    elif isinstance(dep, JarDependency):
                        jarid = ''
                        for key in CacheManager._JAR_HASH_KEYS:
                            jarid += str(getattr(dep, key))
                        dependency_keys.add(jarid)
                    else:
                        dependency_keys.add(str(dep))
                        # TODO(John Sirois): Hashing on external targets should have a formal api - we happen to
                        # know jars are special and python requirements __str__ works for this purpose.
            cache_key = self._key_for(target, dependency_keys)
            id_to_hash[target.id] = cache_key.hash

            # Create a VersionedTarget corresponding to @target.
            versioned_target = VersionedTarget(self, target, cache_key)

            # Add the new VersionedTarget to the list of computed VersionedTargets.
            versioned_targets.append(versioned_target)

            # Add to the mapping from Targets to VersionedTargets, for use in hooking up VersionedTarget dependencies below.
            versioned_targets_by_target[target] = versioned_target

        # Having created all applicable VersionedTargets, now we build the VersionedTarget dependency graph, looking
        # through targets that don't correspond to VersionedTargets themselves.
        versioned_target_deps_by_target = {}

        def get_versioned_target_deps_for_target(target):
            # For every dependency of @target, we will store its corresponding VersionedTarget here. For dependencies that
            # don't correspond to a VersionedTarget (e.g. pass-through dependency wrappers), we will resolve their actual
            # dependencies and find VersionedTargets for them.
            versioned_target_deps = set([])
            if hasattr(target, 'dependencies'):
                for dep in target.dependencies:
                    for dependency in dep.resolve():
                        if dependency in versioned_targets_by_target:
                            # If there exists a VersionedTarget corresponding to this Target, store it and continue.
                            versioned_target_deps.add(
                                versioned_targets_by_target[dependency])
                        elif dependency in versioned_target_deps_by_target:
                            # Otherwise, see if we've already resolved this dependency to the VersionedTargets it depends on, and use
                            # those.
                            versioned_target_deps.update(
                                versioned_target_deps_by_target[dependency])
                        else:
                            # Otherwise, compute the VersionedTargets that correspond to this dependency's dependencies, cache and
                            # use the computed result.
                            versioned_target_deps_by_target[
                                dependency] = get_versioned_target_deps_for_target(
                                    dependency)
                            versioned_target_deps.update(
                                versioned_target_deps_by_target[dependency])

            # Return the VersionedTarget dependencies that this target's VersionedTarget should depend on.
            return versioned_target_deps

        # Initialize all VersionedTargets to point to the VersionedTargets they depend on.
        for versioned_target in versioned_targets:
            versioned_target.dependencies = get_versioned_target_deps_for_target(
                versioned_target.target)

        return versioned_targets

    def needs_update(self, cache_key):
        return self._invalidator.needs_update(cache_key)

    def _order_target_list(self, targets):
        """Orders the targets topologically, from least to most dependent."""
        target_ids = set([x.id for x in targets])

        # Most to least dependent.
        reverse_ordered_targets_and_deps = InternalTarget.sort_targets(targets)
        # Least to most dependent. We must build in this order.
        ordered_targets_and_deps = reversed(reverse_ordered_targets_and_deps)
        # Return just the ones that were originally in targets.
        return filter(lambda x: x.id in target_ids, ordered_targets_and_deps)

    def _key_for(self, target, dependency_keys):
        def fingerprint_extra(sha):
            sha.update(self._extra_data)
            for key in sorted(
                    dependency_keys
            ):  # Sort to ensure hashing in a consistent order.
                sha.update(key)

        return self._cache_key_generator.key_for_target(
            target, sources=self._sources, fingerprint_extra=fingerprint_extra)

    _JAR_HASH_KEYS = ('org', 'name', 'rev', 'force', 'excludes', 'transitive',
                      '_configurations', 'artifacts')
Exemplo n.º 10
0
class PythonChroot(object):
    _VALID_DEPENDENCIES = {
        PythonLibrary: 'libraries',
        PythonRequirement: 'reqs',
        PythonBinary: 'binaries',
        PythonThriftLibrary: 'thrifts',
        PythonAntlrLibrary: 'antlrs',
        PythonTests: 'tests'
    }

    MEMOIZED_THRIFTS = {}

    class InvalidDependencyException(Exception):
        def __init__(self, target):
            Exception.__init__(
                self, "Not a valid Python dependency! Found: %s" % target)

    def __init__(self,
                 target,
                 root_dir,
                 extra_targets=None,
                 builder=None,
                 interpreter=None,
                 conn_timeout=None):
        self._config = Config.load()
        self._target = target
        self._root = root_dir
        self._interpreter = interpreter or PythonInterpreter.get()
        self._extra_targets = list(
            extra_targets) if extra_targets is not None else []
        self._resolver = MultiResolver(self._config,
                                       target,
                                       conn_timeout=conn_timeout)
        self._builder = builder or PEXBuilder(tempfile.mkdtemp(),
                                              interpreter=self._interpreter)

        # Note: unrelated to the general pants artifact cache.
        self._egg_cache_root = os.path.join(
            self._config.get('python-setup', 'artifact_cache'),
            str(self._interpreter.identity))

        self._key_generator = CacheKeyGenerator()
        self._build_invalidator = BuildInvalidator(self._egg_cache_root)

    def __del__(self):
        if os.getenv('PANTS_LEAVE_CHROOT') is None:
            safe_rmtree(self.path())
        else:
            self.debug('Left chroot at %s' % self.path())

    @property
    def builder(self):
        return self._builder

    def debug(self, msg, indent=0):
        if os.getenv('PANTS_VERBOSE') is not None:
            print('%s%s' % (' ' * indent, msg))

    def path(self):
        return self._builder.path()

    def _dump_library(self, library):
        def copy_to_chroot(base, path, add_function):
            src = os.path.join(self._root, base, path)
            add_function(src, path)

        self.debug('  Dumping library: %s' % library)
        for filename in library.sources:
            copy_to_chroot(library.target_base, filename,
                           self._builder.add_source)
        for filename in library.resources:
            copy_to_chroot(library.target_base, filename,
                           self._builder.add_resource)

    def _dump_requirement(self, req, dynamic, repo):
        self.debug('  Dumping requirement: %s%s%s' %
                   (str(req), ' (dynamic)' if dynamic else '',
                    ' (repo: %s)' % repo if repo else ''))
        self._builder.add_requirement(req, dynamic, repo)

    def _dump_distribution(self, dist):
        self.debug('  Dumping distribution: .../%s' %
                   os.path.basename(dist.location))
        self._builder.add_distribution(dist)

    def _generate_requirement(self, library, builder_cls):
        library_key = self._key_generator.key_for_target(library)
        builder = builder_cls(library, self._root, self._config,
                              '-' + library_key.hash[:8])

        cache_dir = os.path.join(self._egg_cache_root, library_key.id)
        if self._build_invalidator.needs_update(library_key):
            sdist = builder.build(interpreter=self._interpreter)
            safe_mkdir(cache_dir)
            shutil.copy(sdist, os.path.join(cache_dir,
                                            os.path.basename(sdist)))
            self._build_invalidator.update(library_key)

        with ParseContext.temp():
            return PythonRequirement(builder.requirement_string(),
                                     repository=cache_dir,
                                     use_2to3=True)

    def _generate_thrift_requirement(self, library):
        return self._generate_requirement(library, PythonThriftBuilder)

    def _generate_antlr_requirement(self, library):
        return self._generate_requirement(library, PythonAntlrBuilder)

    def resolve(self, targets):
        children = defaultdict(OrderedSet)

        def add_dep(trg):
            if trg.is_concrete:
                for target_type, target_key in self._VALID_DEPENDENCIES.items(
                ):
                    if isinstance(trg, target_type):
                        children[target_key].add(trg)
                        return
            raise self.InvalidDependencyException(trg)

        for target in targets:
            target.walk(add_dep)
        return children

    def dump(self):
        self.debug('Building PythonBinary %s:' % self._target)

        targets = self.resolve([self._target] + self._extra_targets)

        for lib in targets['libraries'] | targets['binaries']:
            self._dump_library(lib)

        generated_reqs = OrderedSet()
        if targets['thrifts']:
            for thr in set(targets['thrifts']):
                if thr not in self.MEMOIZED_THRIFTS:
                    self.MEMOIZED_THRIFTS[
                        thr] = self._generate_thrift_requirement(thr)
                generated_reqs.add(self.MEMOIZED_THRIFTS[thr])
            with ParseContext.temp():
                # trick pants into letting us add this python requirement, otherwise we get
                # TargetDefinitionException: Error in target BUILD.temp:thrift: duplicate to
                # PythonRequirement(thrift)
                #
                # TODO(wickman) Instead of just blindly adding a PythonRequirement for thrift, we
                # should first detect if any explicit thrift requirements have been added and use
                # those.  Only if they have not been supplied should we auto-inject it.
                generated_reqs.add(
                    PythonRequirement(
                        'thrift',
                        use_2to3=True,
                        name='thrift-' +
                        ''.join(random.sample('0123456789abcdef' * 8, 8))))

        for antlr in targets['antlrs']:
            generated_reqs.add(self._generate_antlr_requirement(antlr))

        targets['reqs'] |= generated_reqs
        for req in targets['reqs']:
            if not req.should_build(self._interpreter.python,
                                    Platform.current()):
                self.debug('Skipping %s based upon version filter' % req)
                continue
            self._dump_requirement(req._requirement, False, req._repository)

        reqs_to_build = (
            req for req in targets['reqs']
            if req.should_build(self._interpreter.python, Platform.current()))
        for dist in self._resolver.resolve(reqs_to_build,
                                           interpreter=self._interpreter):
            self._dump_distribution(dist)

        if len(targets['binaries']) > 1:
            print('WARNING: Target has multiple python_binary targets!',
                  file=sys.stderr)

        return self._builder
Exemplo n.º 11
0
class CacheManager(object):
  """Manages cache checks, updates and invalidation keeping track of basic change
  and invalidation statistics.
  """
  def __init__(self, cache_key_generator, build_invalidator_dir,
               invalidate_dependents, extra_data, only_externaldeps):
    self._cache_key_generator = cache_key_generator
    self._invalidate_dependents = invalidate_dependents
    self._extra_data = pickle.dumps(extra_data)  # extra_data may be None.
    self._sources = NO_SOURCES if only_externaldeps else TARGET_SOURCES

    self._invalidator = BuildInvalidator(build_invalidator_dir)

  def update(self, vts):
    """Mark a changed or invalidated VersionedTargetSet as successfully processed."""
    for cache_key in vts.per_target_cache_keys:
      self._invalidator.update(cache_key)
    self._invalidator.update(vts.cache_key)

  def check(self, targets, partition_size_hint):
    """Checks whether each of the targets has changed and invalidates it if so.

    Returns a list of VersionedTargetSet objects (either valid or invalid). The returned sets 'cover'
    the input targets, possibly partitioning them, and are in topological order.
    The caller can inspect these in order and, e.g., rebuild the invalid ones.
    """
    all_vts = self._validate(targets)
    invalid_vts = filter(lambda vt: not vt.valid, all_vts)
    all_vts_partitioned = self._partition_versioned_targets(all_vts, partition_size_hint)
    invalid_vts_partitioned = self._partition_versioned_targets(invalid_vts, partition_size_hint)
    return InvalidationCheck(all_vts, all_vts_partitioned, invalid_vts, invalid_vts_partitioned)

  def _validate(self, targets):
    """Validate each target.

    Returns a topologically ordered set of VersionedTargets, each representing one input target.
    """
    # We must check the targets in this order, to ensure correctness if invalidate_dependents=True, since
    # we use earlier cache keys to compute later cache keys in this case.
    ordered_targets = self._order_target_list(targets)
    versioned_targets = []

    # Map from id to current fingerprint of the target with that id. We update this as we iterate, in
    # topological order, so when handling a target, this will already contain all its deps (in this round).
    id_to_hash = {}

    for target in ordered_targets:
      dependency_keys = set()
      if self._invalidate_dependents and hasattr(target, 'dependencies'):
        # Note that we only need to do this for the immediate deps, because those will already
        # reflect changes in their own deps.
        for dep in target.dependencies:
          # We rely on the fact that any deps have already been processed, either in an earlier round or
          # because they came first in ordered_targets.
          if isinstance(dep, Target):
            hash = id_to_hash.get(dep.id, None)
            if hash is None:
              # It may have been processed in a prior round, and therefore the hash should
              # have been written out by the invalidator.
              hash = self._invalidator.existing_hash(dep.id)
              # Note that hash may be None here, indicating that the dependency will not be processed
              # until a later phase. For example, if a codegen target depends on a library target (because
              # the generated code needs that library).
            if hash is not None:
              dependency_keys.add(hash)
          elif isinstance(dep, JarDependency):
            jarid = ''
            for key in CacheManager._JAR_HASH_KEYS:
              jarid += str(getattr(dep, key))
            dependency_keys.add(jarid)
          else:
            dependency_keys.add(str(dep))
            # TODO(John Sirois): Hashing on external targets should have a formal api - we happen to
            # know jars are special and python requirements __str__ works for this purpose.
      cache_key = self._key_for(target, dependency_keys)
      id_to_hash[target.id] = cache_key.hash
      versioned_targets.append(VersionedTargetSet(self, [target], [cache_key]))

    return versioned_targets

  def needs_update(self, cache_key):
    return self._invalidator.needs_update(cache_key)

  def _order_target_list(self, targets):
    """Orders the targets topologically, from least to most dependent."""
    target_ids = set([x.id for x in targets])

    # Most to least dependent.
    reverse_ordered_targets_and_deps = InternalTarget.sort_targets(targets)
    # Least to most dependent. We must build in this order.
    ordered_targets_and_deps = reversed(reverse_ordered_targets_and_deps)
    # Return just the ones that were originally in targets.
    return filter(lambda x: x.id in target_ids, ordered_targets_and_deps)

  def _partition_versioned_targets(self, versioned_targets, partition_size_hint):
    """Groups versioned targets so that each group has roughly the same number of sources.

    versioned_targets is a list of VersionedTargetSet objects  [ vt1, vt2, vt3, vt4, vt5, vt6, ...].

    Returns a list of VersionedTargetSet objects, e.g., [ VT1, VT2, VT3, ...] representing the
    same underlying targets. E.g., VT1 is the combination of [vt1, vt2, vt3], VT2 is the combination
    of [vt4, vt5] and VT3 is [vt6].

    The new versioned targets are chosen to have roughly partition_size_hint sources.

    This is useful as a compromise between flat mode, where we build all targets in a
    single compiler invocation, and non-flat mode, where we invoke a compiler for each target,
    which may lead to lots of compiler startup overhead. A task can choose instead to build one
    group at a time.
    """
    res = []

    # Hack around the python outer scope problem.
    class VtGroup(object):
      def __init__(self):
        self.vts = []
        self.total_sources = 0

    current_group = VtGroup()

    def add_to_current_group(vt):
      current_group.vts.append(vt)
      current_group.total_sources += vt.cache_key.num_sources

    def close_current_group():
      if len(current_group.vts) > 0:
        new_vt = self._combine_versioned_targets(current_group.vts)
        res.append(new_vt)
        current_group.vts = []
        current_group.total_sources = 0

    for vt in versioned_targets:
      add_to_current_group(vt)
      if current_group.total_sources > partition_size_hint:
        if current_group.total_sources > 1.5 * partition_size_hint and len(current_group.vts) > 1:
          # Too big. Close the current group without this vt and add it to the next one.
          current_group.vts.pop()
          close_current_group()
          add_to_current_group(vt)
        else:
          close_current_group()
    close_current_group()  # Close the last group, if any.

    return res

  def _combine_versioned_targets(self, vts):
    targets = []
    for vt in vts:
      targets.extend(vt.targets)
    return VersionedTargetSet(self, targets, [vt.cache_key for vt in vts])

  def _key_for(self, target, dependency_keys):
    def fingerprint_extra(sha):
      sha.update(self._extra_data)
      for key in dependency_keys:
        sha.update(key)

    return self._cache_key_generator.key_for_target(
      target,
      sources=self._sources,
      fingerprint_extra=fingerprint_extra
    )

  _JAR_HASH_KEYS = (
    'org',
    'name',
    'rev',
    'force',
    'excludes',
    'transitive',
    '_configurations',
    'artifacts'
    )
Exemplo n.º 12
0
class CacheManager(object):
    """Manages cache checks, updates and invalidation keeping track of basic change
  and invalidation statistics.
  Note that this is distinct from the ArtifactCache concept, and should probably be renamed.
  """

    def __init__(
        self, cache_key_generator, build_invalidator_dir, invalidate_dependents, extra_data, only_externaldeps
    ):
        self._cache_key_generator = cache_key_generator
        self._invalidate_dependents = invalidate_dependents
        self._extra_data = pickle.dumps(extra_data)  # extra_data may be None.
        self._sources = NO_SOURCES if only_externaldeps else TARGET_SOURCES

        self._invalidator = BuildInvalidator(build_invalidator_dir)

    def update(self, vts):
        """Mark a changed or invalidated VersionedTargetSet as successfully processed."""
        for vt in vts.versioned_targets:
            self._invalidator.update(vt.cache_key)
            vt.valid = True
        self._invalidator.update(vts.cache_key)
        vts.valid = True

    def force_invalidate(self, vts):
        """Force invalidation of a VersionedTargetSet."""
        for vt in vts.versioned_targets:
            self._invalidator.force_invalidate(vt.cache_key)
            vt.valid = False
        self._invalidator.force_invalidate(vts.cache_key)
        vts.valid = False

    def check(self, targets, partition_size_hint=None):
        """Checks whether each of the targets has changed and invalidates it if so.

    Returns a list of VersionedTargetSet objects (either valid or invalid). The returned sets 'cover'
    the input targets, possibly partitioning them, and are in topological order.
    The caller can inspect these in order and, e.g., rebuild the invalid ones.
    """
        all_vts = self._sort_and_validate_targets(targets)
        invalid_vts = filter(lambda vt: not vt.valid, all_vts)
        return InvalidationCheck(all_vts, invalid_vts, partition_size_hint)

    def _sort_and_validate_targets(self, targets):
        """Validate each target.

    Returns a topologically ordered set of VersionedTargets, each representing one input target.
    """
        # We must check the targets in this order, to ensure correctness if invalidate_dependents=True, since
        # we use earlier cache keys to compute later cache keys in this case.
        ordered_targets = self._order_target_list(targets)

        # This will be a list of VersionedTargets that correspond to @targets.
        versioned_targets = []

        # This will be a mapping from each target to its corresponding VersionedTarget.
        versioned_targets_by_target = {}

        # Map from id to current fingerprint of the target with that id. We update this as we iterate, in
        # topological order, so when handling a target, this will already contain all its deps (in this round).
        id_to_hash = {}

        for target in ordered_targets:
            dependency_keys = set()
            if self._invalidate_dependents and hasattr(target, "dependencies"):
                # Note that we only need to do this for the immediate deps, because those will already
                # reflect changes in their own deps.
                for dep in target.dependencies:
                    # We rely on the fact that any deps have already been processed, either in an earlier round or
                    # because they came first in ordered_targets.
                    if isinstance(dep, Target):
                        hash = id_to_hash.get(dep.id, None)
                        if hash is None:
                            # It may have been processed in a prior round, and therefore the hash should
                            # have been written out by the invalidator.
                            hash = self._invalidator.existing_hash(dep.id)
                            # Note that hash may be None here, indicating that the dependency will not be processed
                            # until a later phase. For example, if a codegen target depends on a library target (because
                            # the generated code needs that library).
                        if hash is not None:
                            dependency_keys.add(hash)
                    elif isinstance(dep, JarDependency):
                        jarid = ""
                        for key in CacheManager._JAR_HASH_KEYS:
                            jarid += str(getattr(dep, key))
                        dependency_keys.add(jarid)
                    else:
                        dependency_keys.add(str(dep))
                        # TODO(John Sirois): Hashing on external targets should have a formal api - we happen to
                        # know jars are special and python requirements __str__ works for this purpose.
            cache_key = self._key_for(target, dependency_keys)
            id_to_hash[target.id] = cache_key.hash

            # Create a VersionedTarget corresponding to @target.
            versioned_target = VersionedTarget(self, target, cache_key)

            # Add the new VersionedTarget to the list of computed VersionedTargets.
            versioned_targets.append(versioned_target)

            # Add to the mapping from Targets to VersionedTargets, for use in hooking up VersionedTarget dependencies below.
            versioned_targets_by_target[target] = versioned_target

        # Having created all applicable VersionedTargets, now we build the VersionedTarget dependency graph, looking
        # through targets that don't correspond to VersionedTargets themselves.
        versioned_target_deps_by_target = {}

        def get_versioned_target_deps_for_target(target):
            # For every dependency of @target, we will store its corresponding VersionedTarget here. For dependencies that
            # don't correspond to a VersionedTarget (e.g. pass-through dependency wrappers), we will resolve their actual
            # dependencies and find VersionedTargets for them.
            versioned_target_deps = set([])
            if hasattr(target, "dependencies"):
                for dep in target.dependencies:
                    for dependency in dep.resolve():
                        if dependency in versioned_targets_by_target:
                            # If there exists a VersionedTarget corresponding to this Target, store it and continue.
                            versioned_target_deps.add(versioned_targets_by_target[dependency])
                        elif dependency in versioned_target_deps_by_target:
                            # Otherwise, see if we've already resolved this dependency to the VersionedTargets it depends on, and use
                            # those.
                            versioned_target_deps.update(versioned_target_deps_by_target[dependency])
                        else:
                            # Otherwise, compute the VersionedTargets that correspond to this dependency's dependencies, cache and
                            # use the computed result.
                            versioned_target_deps_by_target[dependency] = get_versioned_target_deps_for_target(
                                dependency
                            )
                            versioned_target_deps.update(versioned_target_deps_by_target[dependency])

            # Return the VersionedTarget dependencies that this target's VersionedTarget should depend on.
            return versioned_target_deps

        # Initialize all VersionedTargets to point to the VersionedTargets they depend on.
        for versioned_target in versioned_targets:
            versioned_target.dependencies = get_versioned_target_deps_for_target(versioned_target.target)

        return versioned_targets

    def needs_update(self, cache_key):
        return self._invalidator.needs_update(cache_key)

    def _order_target_list(self, targets):
        """Orders the targets topologically, from least to most dependent."""
        target_ids = set([x.id for x in targets])

        # Most to least dependent.
        reverse_ordered_targets_and_deps = InternalTarget.sort_targets(targets)
        # Least to most dependent. We must build in this order.
        ordered_targets_and_deps = reversed(reverse_ordered_targets_and_deps)
        # Return just the ones that were originally in targets.
        return filter(lambda x: x.id in target_ids, ordered_targets_and_deps)

    def _key_for(self, target, dependency_keys):
        def fingerprint_extra(sha):
            sha.update(self._extra_data)
            for key in sorted(dependency_keys):  # Sort to ensure hashing in a consistent order.
                sha.update(key)

        return self._cache_key_generator.key_for_target(
            target, sources=self._sources, fingerprint_extra=fingerprint_extra
        )

    _JAR_HASH_KEYS = ("org", "name", "rev", "force", "excludes", "transitive", "_configurations", "artifacts")
Exemplo n.º 13
0
class CacheManager(object):
  """
    Manages cache checks, updates and invalidation keeping track of basic change
    and invalidation statistics.
  """
  def __init__(self, cache_key_generator, build_invalidator_dir, targets, invalidate_dependents,
               extra_data, only_externaldeps):
    self._cache_key_generator = cache_key_generator
    self._targets = set(targets)
    self._invalidate_dependents = invalidate_dependents
    self._extra_data = pickle.dumps(extra_data)  # extra_data may be None.
    self._sources = NO_SOURCES if only_externaldeps else TARGET_SOURCES
    self._invalidator = BuildInvalidator(build_invalidator_dir)

  def check(self, targets):
    """Checks whether each of the targets has changed and invalidates it if so.

    Returns a list of VersionedTargetSets, one per input target, regardless of whether
    it was invalidated or not. Note that the returned list is in topologically-sorted order.
    That is, if B depends on A then B is later than A.
    """

    # We must check the targets in this order, to ensure correctness if invalidate_dependents=True, since
    # we use earlier cache keys to compute later cache keys in this case.
    ordered_targets = self._order_target_list(targets)
    versioned_targets = []

    # Map from id to current fingerprint of the target with that id. We update this as we iterate, in
    # topological order, so when handling a target, this will already contain all its deps (in this round).
    id_to_hash = {}

    for target in ordered_targets:
      dependency_keys = set()
      if self._invalidate_dependents and hasattr(target, 'dependencies'):
        # Note that we only need to do this for the immediate deps, because those will already
        # reflect changes in their own deps.
        for dep in target.dependencies:
          # We rely on the fact that any deps have already been processed, either in an earlier round or
          # because they came first in ordered_targets.
          if isinstance(dep, Target):
            hash = id_to_hash.get(dep.id, None)
            if hash is None:
              # It may have been processed in a prior round, and therefore the hash should
              # have been written out by the invalidator.
              hash = self._invalidator.existing_hash(dep.id)
              # Note that hash may be None here, indicating that the dependency will not be processed
              # until a later phase. For example, if a codegen target depends on a library target (because
              # the generated code needs that library).
            if hash is not None:
              dependency_keys.add(hash)
          elif isinstance(dep, JarDependency):
            jarid = ''
            for key in CacheManager._JAR_HASH_KEYS:
              jarid += str(getattr(dep, key))
            dependency_keys.add(jarid)
          else:
            dependency_keys.add(str(dep))
          # TODO(John Sirois): Hashing on external targets should have a formal api - we happen to
          # know jars are special and python requirements __str__ works for this purpose.
      cache_key = self._key_for(target, dependency_keys)
      id_to_hash[target.id] = cache_key.hash
      if self._invalidator.needs_update(cache_key):
        self._invalidator.invalidate(cache_key)
        valid = False
      else:
        valid = True
      versioned_targets.append(VersionedTargetSet([target], cache_key, valid))

    return versioned_targets

  def update(self, cache_key):
    """Mark a changed or invalidated target as successfully processed."""
    self._invalidator.update(cache_key)

  def _order_target_list(self, targets):
    """Orders the targets topologically, from least to most dependent."""
    target_ids = set([x.id for x in targets])
    ordered_targets_and_deps = reversed(InternalTarget.sort_targets(targets))
    # Return just the ones that were originally in targets.
    return filter(lambda x: x.id in target_ids, ordered_targets_and_deps)

  def _key_for(self, target, dependency_keys):
    def fingerprint_extra(sha):
      sha.update(self._extra_data)
      for key in dependency_keys:
        sha.update(key)

    return self._cache_key_generator.key_for_target(
      target,
      sources=self._sources,
      fingerprint_extra=fingerprint_extra
    )

  _JAR_HASH_KEYS = (
    'org',
    'name',
    'rev',
    'force',
    'excludes',
    'transitive',
    'ext',
    'url',
    '_configurations'
    )
Exemplo n.º 14
0
class PythonChroot(object):
  _VALID_DEPENDENCIES = {
    PythonLibrary: 'libraries',
    PythonRequirement: 'reqs',
    PythonBinary: 'binaries',
    PythonThriftLibrary: 'thrifts',
    PythonAntlrLibrary: 'antlrs',
    PythonTests: 'tests'
  }

  MEMOIZED_THRIFTS = {}

  class InvalidDependencyException(Exception):
    def __init__(self, target):
      Exception.__init__(self, "Not a valid Python dependency! Found: %s" % target)

  def __init__(self, target, root_dir, extra_targets=None, builder=None, interpreter=None,
      conn_timeout=None):
    self._config = Config.load()
    self._target = target
    self._root = root_dir
    self._interpreter = interpreter or PythonInterpreter.get()
    self._extra_targets = list(extra_targets) if extra_targets is not None else []
    self._resolver = MultiResolver(self._config, target, conn_timeout=conn_timeout)
    self._builder = builder or PEXBuilder(tempfile.mkdtemp(), interpreter=self._interpreter)

    # Note: unrelated to the general pants artifact cache.
    self._egg_cache_root = os.path.join(self._config.get('python-setup', 'artifact_cache'),
                                        str(self._interpreter.identity))

    self._key_generator = CacheKeyGenerator()
    self._build_invalidator = BuildInvalidator( self._egg_cache_root)


  def __del__(self):
    if os.getenv('PANTS_LEAVE_CHROOT') is None:
      safe_rmtree(self.path())
    else:
      self.debug('Left chroot at %s' % self.path())

  @property
  def builder(self):
    return self._builder

  def debug(self, msg, indent=0):
    if os.getenv('PANTS_VERBOSE') is not None:
      print('%s%s' % (' ' * indent, msg))

  def path(self):
    return self._builder.path()

  def _dump_library(self, library):
    def copy_to_chroot(base, path, add_function):
      src = os.path.join(self._root, base, path)
      add_function(src, path)

    self.debug('  Dumping library: %s' % library)
    for filename in library.sources:
      copy_to_chroot(library.target_base, filename, self._builder.add_source)
    for filename in library.resources:
      copy_to_chroot(library.target_base, filename, self._builder.add_resource)

  def _dump_requirement(self, req, dynamic, repo):
    self.debug('  Dumping requirement: %s%s%s' % (str(req),
      ' (dynamic)' if dynamic else '', ' (repo: %s)' % repo if repo else ''))
    self._builder.add_requirement(req, dynamic, repo)

  def _dump_distribution(self, dist):
    self.debug('  Dumping distribution: .../%s' % os.path.basename(dist.location))
    self._builder.add_distribution(dist)

  def _generate_requirement(self, library, builder_cls):
    library_key = self._key_generator.key_for_target(library)
    builder = builder_cls(library, self._root, self._config, '-' + library_key.hash[:8])

    cache_dir = os.path.join(self._egg_cache_root, library_key.id)
    if self._build_invalidator.needs_update(library_key):
      sdist = builder.build(interpreter=self._interpreter)
      safe_mkdir(cache_dir)
      shutil.copy(sdist, os.path.join(cache_dir, os.path.basename(sdist)))
      self._build_invalidator.update(library_key)

    with ParseContext.temp():
      return PythonRequirement(builder.requirement_string(), repository=cache_dir, use_2to3=True)

  def _generate_thrift_requirement(self, library):
    return self._generate_requirement(library, PythonThriftBuilder)

  def _generate_antlr_requirement(self, library):
    return self._generate_requirement(library, PythonAntlrBuilder)

  def resolve(self, targets):
    children = defaultdict(OrderedSet)
    def add_dep(trg):
      if trg.is_concrete:
        for target_type, target_key in self._VALID_DEPENDENCIES.items():
          if isinstance(trg, target_type):
            children[target_key].add(trg)
            return
      raise self.InvalidDependencyException(trg)
    for target in targets:
      target.walk(add_dep)
    return children

  def dump(self):
    self.debug('Building PythonBinary %s:' % self._target)

    targets = self.resolve([self._target] + self._extra_targets)

    for lib in targets['libraries'] | targets['binaries']:
      self._dump_library(lib)

    generated_reqs = OrderedSet()
    if targets['thrifts']:
      for thr in set(targets['thrifts']):
        if thr not in self.MEMOIZED_THRIFTS:
          self.MEMOIZED_THRIFTS[thr] = self._generate_thrift_requirement(thr)
        generated_reqs.add(self.MEMOIZED_THRIFTS[thr])
      with ParseContext.temp():
        # trick pants into letting us add this python requirement, otherwise we get
        # TargetDefinitionException: Error in target BUILD.temp:thrift: duplicate to
        # PythonRequirement(thrift)
        #
        # TODO(wickman) Instead of just blindly adding a PythonRequirement for thrift, we
        # should first detect if any explicit thrift requirements have been added and use
        # those.  Only if they have not been supplied should we auto-inject it.
        generated_reqs.add(PythonRequirement('thrift', use_2to3=True,
            name='thrift-' + ''.join(random.sample('0123456789abcdef' * 8, 8))))

    for antlr in targets['antlrs']:
      generated_reqs.add(self._generate_antlr_requirement(antlr))

    targets['reqs'] |= generated_reqs
    for req in targets['reqs']:
      if not req.should_build(self._interpreter.python, Platform.current()):
        self.debug('Skipping %s based upon version filter' % req)
        continue
      self._dump_requirement(req._requirement, False, req._repository)

    reqs_to_build = (req for req in targets['reqs']
        if req.should_build(self._interpreter.python, Platform.current()))
    for dist in self._resolver.resolve(reqs_to_build, interpreter=self._interpreter):
      self._dump_distribution(dist)

    if len(targets['binaries']) > 1:
      print('WARNING: Target has multiple python_binary targets!', file=sys.stderr)

    return self._builder
Exemplo n.º 15
0
class CacheManager(object):
    """
    Manages cache checks, updates and invalidation keeping track of basic change
    and invalidation statistics.
  """
    def __init__(self, cache_key_generator, build_invalidator_dir, targets,
                 invalidate_dependents, extra_data, only_externaldeps):
        self._cache_key_generator = cache_key_generator
        self._targets = set(targets)
        self._invalidate_dependents = invalidate_dependents
        self._extra_data = pickle.dumps(extra_data)  # extra_data may be None.
        self._sources = NO_SOURCES if only_externaldeps else TARGET_SOURCES
        self._invalidator = BuildInvalidator(build_invalidator_dir)

    def check(self, targets):
        """Checks whether each of the targets has changed and invalidates it if so.

    Returns a list of VersionedTargetSets, one per input target, regardless of whether
    it was invalidated or not. Note that the returned list is in topologically-sorted order.
    That is, if B depends on A then B is later than A.
    """

        # We must check the targets in this order, to ensure correctness if invalidate_dependents=True, since
        # we use earlier cache keys to compute later cache keys in this case.
        ordered_targets = self._order_target_list(targets)
        versioned_targets = []

        # Map from id to current fingerprint of the target with that id. We update this as we iterate, in
        # topological order, so when handling a target, this will already contain all its deps (in this round).
        id_to_hash = {}

        for target in ordered_targets:
            dependency_keys = set()
            if self._invalidate_dependents and hasattr(target, 'dependencies'):
                # Note that we only need to do this for the immediate deps, because those will already
                # reflect changes in their own deps.
                for dep in target.dependencies:
                    # We rely on the fact that any deps have already been processed, either in an earlier round or
                    # because they came first in ordered_targets.
                    if isinstance(dep, Target):
                        hash = id_to_hash.get(dep.id, None)
                        if hash is None:
                            # It may have been processed in a prior round, and therefore the hash should
                            # have been written out by the invalidator.
                            hash = self._invalidator.existing_hash(dep.id)
                            # Note that hash may be None here, indicating that the dependency will not be processed
                            # until a later phase. For example, if a codegen target depends on a library target (because
                            # the generated code needs that library).
                        if hash is not None:
                            dependency_keys.add(hash)
                    elif isinstance(dep, JarDependency):
                        jarid = ''
                        for key in CacheManager._JAR_HASH_KEYS:
                            jarid += str(getattr(dep, key))
                        dependency_keys.add(jarid)
                    else:
                        dependency_keys.add(str(dep))
                    # TODO(John Sirois): Hashing on external targets should have a formal api - we happen to
                    # know jars are special and python requirements __str__ works for this purpose.
            cache_key = self._key_for(target, dependency_keys)
            id_to_hash[target.id] = cache_key.hash
            if self._invalidator.needs_update(cache_key):
                self._invalidator.invalidate(cache_key)
                valid = False
            else:
                valid = True
            versioned_targets.append(
                VersionedTargetSet([target], cache_key, valid))

        return versioned_targets

    def update(self, cache_key):
        """Mark a changed or invalidated target as successfully processed."""
        self._invalidator.update(cache_key)

    def _order_target_list(self, targets):
        """Orders the targets topologically, from least to most dependent."""
        target_ids = set([x.id for x in targets])
        ordered_targets_and_deps = reversed(
            InternalTarget.sort_targets(targets))
        # Return just the ones that were originally in targets.
        return filter(lambda x: x.id in target_ids, ordered_targets_and_deps)

    def _key_for(self, target, dependency_keys):
        def fingerprint_extra(sha):
            sha.update(self._extra_data)
            for key in dependency_keys:
                sha.update(key)

        return self._cache_key_generator.key_for_target(
            target, sources=self._sources, fingerprint_extra=fingerprint_extra)

    _JAR_HASH_KEYS = ('org', 'name', 'rev', 'force', 'excludes', 'transitive',
                      'ext', 'url', '_configurations')
Exemplo n.º 16
0
def test_env(content=TEST_CONTENT):
    with temporary_dir() as d:
        with tempfile.NamedTemporaryFile() as f:
            f.write(content)
            f.flush()
            yield f, CacheKeyGenerator(), BuildInvalidator(d)