Skip to content

Instantly share code, notes, and snippets.

@dt
Last active August 29, 2015 14:06
Show Gist options
  • Select an option

  • Save dt/55b1a37c35cde9812d0e to your computer and use it in GitHub Desktop.

Select an option

Save dt/55b1a37c35cde9812d0e to your computer and use it in GitHub Desktop.
BUILD graph rules
# Copyright 2014 Foursquare Labs Inc. All Rights Reserved.
from pants.backend.core.tasks.task import Task
class Tagger(Task):
@classmethod
def product_types(cls):
return ['tagged_build_graph']
def execute(self):
prefixes = self.context.config.getdict('target-tags', 'by-prefix') or {}
if prefixes:
for target in self.context.targets():
for prefix in prefixes:
if target.address.spec.startswith(prefix):
target._tags |= set(prefixes[prefix])
# Copyright 2014 Foursquare Labs Inc. All Rights Reserved.
from pants.backend.core.tasks.task import Task
class Validate(Task):
def prepare(self, round_manager):
round_manager.require_data('tagged_build_graph')
@classmethod
def product_types(cls):
return ['validated_build_graph']
def execute(self):
violations = []
for target in self.context.targets():
if 'exempt' not in target.tags:
violations.extend(self.dependee_violations(target))
violations.extend(self.banned_tag_violations(target))
violations.extend(self.required_tag_violations(target))
direct_violations = [v for v in violations if v.direct]
# If there are direct violations, only print them since in this case
# transitive violations are likely spurious.
if direct_violations:
violations = direct_violations
for v in violations:
self.context.log.error(v.msg())
assert(not violations)
def extract_matching_tags(self, prefix, target):
return set([tag.split(':', 1)[1] for tag in target.tags if tag.startswith(prefix)])
def nonexempt_deps(self, target):
closure = self.context.build_graph.transitive_subgraph_of_addresses([target.address])
return [dep for dep in closure if dep is not target and 'exempt' not in dep.tags]
def dependee_violations(self, target):
for dep in self.nonexempt_deps(target):
for must_have in self.extract_matching_tags('dependees_must_have:', dep):
if must_have not in target.tags:
yield PrivacyViolation(target, dep, must_have)
def banned_tag_violations(self, target):
banned_tags = self.extract_matching_tags('dependencies_cannot_have:', target)
if banned_tags:
for dep in self.nonexempt_deps(target):
for banned in banned_tags:
if banned in dep.tags:
yield BannedTag(target, dep, banned)
def required_tag_violations(self, target):
required_tags = self.extract_matching_tags('dependencies_must_have:', target)
if required_tags:
for dep in self.nonexempt_deps(target):
for required in required_tags:
if required not in dep.tags:
yield MissingTag(target, dep, required)
class BuildGraphRuleViolation(object):
def __init__(self, target, dep, tag):
self.target = target
self.dep = dep
self.tag = tag
self.direct = dep in target.dependencies
class BannedTag(BuildGraphRuleViolation):
def msg(self):
return '%s bans dependency on %s (via tag: %s)' % \
(self.target.address.spec, self.dep.address.spec, self.tag)
class MissingTag(BuildGraphRuleViolation):
def msg(self):
return '%s requires dependencies to have tag %s and thus cannot depend on %s' \
% (self.target.address.spec, self.tag, self.dep.address.spec)
class PrivacyViolation(BuildGraphRuleViolation):
def msg(self):
return '%s cannot depend on %s without having tag %s' \
% (self.target.address.spec, self.dep.address.spec, self.tag)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment