def test_locate(self): with pytest.raises(ParseContext.ContextError): ParseContext.locate() with temporary_dir() as root_dir: a_context = ParseContext(create_buildfile(root_dir, 'a')) b_context = ParseContext(create_buildfile(root_dir, 'b')) def test_in_a(): self.assertEquals(a_context, ParseContext.locate()) return b_context.do_in_context(lambda: ParseContext.locate()) self.assertEquals(b_context, a_context.do_in_context(test_in_a))
def test_sibling_references(self): with temporary_dir() as root_dir: buildfile = create_buildfile(root_dir, 'a', name='BUILD', content=dedent(""" dependencies(name='util', dependencies=[ jar(org='com.twitter', name='util', rev='0.0.1') ] ) """).strip()) sibling = create_buildfile(root_dir, 'a', name='BUILD.sibling', content=dedent(""" dependencies(name='util-ex', dependencies=[ pants(':util'), jar(org='com.twitter', name='util-ex', rev='0.0.1') ] ) """).strip()) ParseContext(buildfile).parse() utilex = Target.get( Address.parse(root_dir, 'a:util-ex', is_relative=False)) utilex_deps = set(utilex.resolve()) util = Target.get( Address.parse(root_dir, 'a:util', is_relative=False)) util_deps = set(util.resolve()) self.assertEquals(util_deps, util_deps.intersection(utilex_deps))
def get(address): """Returns the specified module target if already parsed; otherwise, parses the buildfile in the context of its parent directory and returns the parsed target.""" def lookup(): return Target._targets_by_address.get(address, None) target = lookup() if target: return target else: ParseContext(address.buildfile).parse() return lookup()
def test_parse(self): with temporary_dir() as root_dir: buildfile = create_buildfile(root_dir, 'a', content=dedent(""" with open('%s/a/b', 'w') as b: b.write('jack spratt') """ % root_dir).strip()) b_file = os.path.join(root_dir, 'a', 'b') self.assertFalse(os.path.exists(b_file)) ParseContext(buildfile).parse() with open(b_file, 'r') as b: self.assertEquals('jack spratt', b.read())
def get_all_addresses(cls, buildfile): """Returns all of the target addresses in the specified buildfile if already parsed; otherwise, parses the buildfile to find all the addresses it contains and then returns them.""" def lookup(): if buildfile in cls._addresses_by_buildfile: return cls._addresses_by_buildfile[buildfile] else: return OrderedSet() addresses = lookup() if addresses: return addresses else: ParseContext(buildfile).parse() return lookup()
def test_on_context_exit(self): with temporary_dir() as root_dir: parse_context = ParseContext(create_buildfile(root_dir, 'a')) with pytest.raises(parse_context.ContextError): parse_context.on_context_exit(lambda: 37) with temporary_dir() as root_dir: buildfile = create_buildfile(root_dir, 'a', content=dedent(""" import os from twitter.pants.base.parse_context import ParseContext def leave_a_trail(file, contents=''): with open(file, 'w') as b: b.write(contents) b_file = os.path.join(os.path.dirname(__file__), 'b') ParseContext.locate().on_context_exit(leave_a_trail, b_file, contents='42') assert not os.path.exists(b_file), 'Expected context exit action to be delayed.' """).strip()) b_file = os.path.join(root_dir, 'a', 'b') self.assertFalse(os.path.exists(b_file)) ParseContext(buildfile).parse() with open(b_file, 'r') as b: self.assertEquals('42', b.read())
def find(cls, target): """Finds the source root for the given target. If none is registered, returns the parent directory of the target's BUILD file. """ target_path = os.path.relpath(target.address.buildfile.parent_path, get_buildroot()) def _find(): for root_dir, types in cls._TYPES_BY_ROOT.items(): if target_path.startswith(root_dir): # The only candidate root for this target. # Validate the target type, if restrictions were specified. if types and not isinstance(target, tuple(types)): # TODO: Find a way to use the BUILD file aliases in the error message, instead # of target.__class__.__name__. E.g., java_tests instead of JavaTests. raise TargetDefinitionException(target, 'Target type %s not allowed under %s' % (target.__class__.__name__, root_dir)) return root_dir return None # Try already registered roots root = _find() if root: return root # Fall back to searching the ancestor path for a root. # TODO(John Sirois): We currently allow for organic growth of maven multi-module layout style # projects (for example) and do not require a global up-front registration of all source roots # and instead do lazy resolution here. This allows for parse cycles that lead to surprising # runtime errors. Re-consider allowing lazy source roots at all. for buildfile in reversed(target.address.buildfile.ancestors()): if buildfile not in cls._SEARCHED: ParseContext(buildfile).parse() cls._SEARCHED.add(buildfile) root = _find() if root: return root # Finally, resolve files relative to the BUILD file parent dir as the target base return target_path
def do_in_context(self, work): return ParseContext(self.address.buildfile).do_in_context(work)
def setup_parser(self, parser, args): self.config = Config.load() Goal.add_global_options(parser) # We support attempting zero or more goals. Multiple goals must be delimited from further # options and non goal args with a '--'. The key permutations we need to support: # ./pants goal => goals # ./pants goal goals => goals # ./pants goal compile src/java/... => compile # ./pants goal compile -x src/java/... => compile # ./pants goal compile src/java/... -x => compile # ./pants goal compile run -- src/java/... => compile, run # ./pants goal compile run -- src/java/... -x => compile, run # ./pants goal compile run -- -x src/java/... => compile, run if not args: args.append('goals') if len(args) == 1 and args[0] in set(['-h', '--help', 'help']): def format_usage(usages): left_colwidth = 0 for left, right in usages: left_colwidth = max(left_colwidth, len(left)) lines = [] for left, right in usages: lines.append(' %s%s%s' % (left, ' ' * (left_colwidth - len(left) + 1), right)) return '\n'.join(lines) usages = [ ("%prog goal goals ([spec]...)", Phase('goals').description), ("%prog goal help [goal] ([spec]...)", Phase('help').description), ("%prog goal [goal] [spec]...", "Attempt goal against one or more targets."), ("%prog goal [goal] ([goal]...) -- [spec]...", "Attempts all the specified goals."), ] parser.set_usage("\n%s" % format_usage(usages)) parser.epilog = ( "Either lists all installed goals, provides extra help for a goal or else " "attempts to achieve the specified goal for the listed targets." """ Note that target specs accept two special forms: [dir]: to include all targets in the specified directory [dir]:: to include all targets found in all BUILD files recursively under the directory""") parser.print_help() sys.exit(0) else: goals, specs = Goal.parse_args(args) self.requested_goals = goals with self.run_tracker.new_workunit(name='setup', labels=[WorkUnit.SETUP]): # Bootstrap goals by loading any configured bootstrap BUILD files with self.check_errors( 'The following bootstrap_buildfiles cannot be loaded:' ) as error: with self.run_tracker.new_workunit(name='bootstrap', labels=[WorkUnit.SETUP ]): for path in self.config.getlist('goals', 'bootstrap_buildfiles', default=[]): try: buildfile = BuildFile( get_buildroot(), os.path.relpath(path, get_buildroot())) ParseContext(buildfile).parse() except (TypeError, ImportError, TaskError, GoalError): error(path, include_traceback=True) except (IOError, SyntaxError): error(path) # Now that we've parsed the bootstrap BUILD files, and know about the SCM system. self.run_tracker.run_info.add_scm_info() # Bootstrap user goals by loading any BUILD files implied by targets. spec_parser = SpecParser(self.root_dir) with self.check_errors( 'The following targets could not be loaded:') as error: with self.run_tracker.new_workunit(name='parse', labels=[WorkUnit.SETUP ]): for spec in specs: try: for target, address in spec_parser.parse(spec): if target: self.targets.append(target) # Force early BUILD file loading if this target is an alias that expands # to others. unused = list(target.resolve()) else: siblings = Target.get_all_addresses( address.buildfile) prompt = 'did you mean' if len( siblings ) == 1 else 'maybe you meant one of these' error('%s => %s?:\n %s' % (address, prompt, '\n '.join( str(a) for a in siblings))) except (TypeError, ImportError, TaskError, GoalError): error(spec, include_traceback=True) except (IOError, SyntaxError, TargetDefinitionException): error(spec) self.phases = [Phase(goal) for goal in goals] rcfiles = self.config.getdefault('rcfiles', type=list, default=[]) if rcfiles: rcfile = RcFile(rcfiles, default_prepend=False, process_default=True) # Break down the goals specified on the command line to the full set that will be run so we # can apply default flags to inner goal nodes. Also break down goals by Task subclass and # register the task class hierarchy fully qualified names so we can apply defaults to # baseclasses. sections = OrderedSet() for phase in Engine.execution_order(self.phases): for goal in phase.goals(): sections.add(goal.name) for clazz in goal.task_type.mro(): if clazz == Task: break sections.add('%s.%s' % (clazz.__module__, clazz.__name__)) augmented_args = rcfile.apply_defaults(sections, args) if augmented_args != args: del args[:] args.extend(augmented_args) sys.stderr.write( "(using pantsrc expansion: pants goal %s)\n" % ' '.join(augmented_args)) Phase.setup_parser(parser, args, self.phases)