def setUp(self): super(BaseTest, self).setUp() Goal.clear() Subsystem.reset() self.real_build_root = BuildRoot().path self.build_root = os.path.realpath(mkdtemp(suffix='_BUILD_ROOT')) self.addCleanup(safe_rmtree, self.build_root) self.pants_workdir = os.path.join(self.build_root, '.pants.d') safe_mkdir(self.pants_workdir) self.options = defaultdict(dict) # scope -> key-value mapping. self.options[''] = { 'pants_workdir': self.pants_workdir, 'pants_supportdir': os.path.join(self.build_root, 'build-support'), 'pants_distdir': os.path.join(self.build_root, 'dist'), 'pants_configdir': os.path.join(self.build_root, 'config'), 'cache_key_gen_version': '0-test', } BuildRoot().path = self.build_root self.addCleanup(BuildRoot().reset) # We need a pants.ini, even if empty. get_buildroot() uses its presence. self.create_file('pants.ini') self._build_configuration = BuildConfiguration() self._build_configuration.register_aliases(self.alias_groups) self.build_file_parser = BuildFileParser(self._build_configuration, self.build_root) self.address_mapper = BuildFileAddressMapper(self.build_file_parser, FilesystemBuildFile) self.build_graph = BuildGraph(address_mapper=self.address_mapper)
def __init__(self, root_dir, options, build_config, run_tracker, reporting, exiter=sys.exit): """ :param str root_dir: The root directory of the pants workspace (aka the "build root"). :param Options options: The global, pre-initialized Options instance. :param BuildConfiguration build_config: A pre-initialized BuildConfiguration instance. :param Runtracker run_tracker: The global, pre-initialized/running RunTracker instance. :param Reporting reporting: The global, pre-initialized Reporting instance. :param func exiter: A function that accepts an exit code value and exits (for tests, Optional). """ self._root_dir = root_dir self._options = options self._build_config = build_config self._run_tracker = run_tracker self._reporting = reporting self._exiter = exiter self._goals = [] self._targets = [] self._requested_goals = self._options.goals self._target_specs = self._options.target_specs self._help_request = self._options.help_request self._global_options = options.for_global_scope() self._tag = self._global_options.tag self._fail_fast = self._global_options.fail_fast # Will be provided through context.address_mapper.build_ignore_patterns. self._spec_excludes = None self._explain = self._global_options.explain self._kill_nailguns = self._global_options.kill_nailguns self._project_tree = self._get_project_tree( self._global_options.build_file_rev) self._build_file_parser = BuildFileParser(self._build_config, self._root_dir) build_ignore_patterns = self._global_options.ignore_patterns or [] build_ignore_patterns.extend( BuildFile._spec_excludes_to_gitignore_syntax( self._root_dir, self._global_options.spec_excludes)) self._address_mapper = BuildFileAddressMapper(self._build_file_parser, self._project_tree, build_ignore_patterns) self._build_graph = BuildGraph(self._address_mapper) self._spec_parser = CmdLineSpecParser( self._root_dir, self._address_mapper, spec_excludes=self._spec_excludes, exclude_target_regexps=self._global_options.exclude_target_regexp)
def scan(self, root=None): """Scans and parses all BUILD files found under ``root``. Only BUILD files found under ``root`` are parsed as roots in the graph, but any dependencies of targets parsed in the root tree's BUILD files will be followed and this may lead to BUILD files outside of ``root`` being parsed and included in the returned build graph. :param string root: The path to scan; by default, the build root. :returns: A new build graph encapsulating the targets found. """ build_graph = BuildGraph(self.address_mapper) for address in self.address_mapper.scan_addresses(root, spec_excludes=self.spec_excludes): build_graph.inject_address_closure(address) return build_graph
def scan(self, root=None): """Scans and parses all BUILD files found under ``root``. Only BUILD files found under ``root`` are parsed as roots in the graph, but any dependencies of targets parsed in the root tree's BUILD files will be followed and this may lead to BUILD files outside of ``root`` being parsed and included in the returned build graph. :param string root: The path to scan; by default, the build root. :returns: A new build graph encapsulating the targets found. """ build_graph = BuildGraph(self.address_mapper) for address in self.address_mapper.scan_addresses(root, spec_excludes=self.spec_excludes): build_graph.inject_address_closure(address) return build_graph
def _filter_by_excludes(self, classpath_target_tuples, root_targets): # Excludes are always applied transitively, so regardless of whether a transitive # set of targets was included here, their closure must be included. closure = BuildGraph.closure(root_targets, bfs=True) excludes = self._excludes.get_for_targets(closure) return [target_tuple for target_tuple in classpath_target_tuples if _not_excluded_filter(excludes)(target_tuple)]
def _filter_by_excludes(self, classpath_target_tuples, root_targets): # Excludes are always applied transitively, so regardless of whether a transitive # set of targets was included here, their closure must be included. closure = BuildGraph.closure(root_targets, bfs=True) excludes = self._excludes.get_for_targets(closure) return [target_tuple for target_tuple in classpath_target_tuples if _not_excluded_filter(excludes)(target_tuple)]
def classpath(self, targets, classpath_prefix=None, classpath_product=None, exclude_scopes=None, include_scopes=None): """Builds a transitive classpath for the given targets. Optionally includes a classpath prefix or building from a non-default classpath product. :param targets: the targets for which to build the transitive classpath. :param classpath_prefix: optional additional entries to prepend to the classpath. :param classpath_product: an optional ClasspathProduct from which to build the classpath. if not specified, the runtime_classpath will be used. :param :class:`pants.build_graph.target_scopes.Scope` exclude_scopes: Exclude targets which have at least one of these scopes on the classpath. :param :class:`pants.build_graph.target_scopes.Scope` include_scopes: Only include targets which have at least one of these scopes on the classpath. Defaults to Scopes.JVM_RUNTIME_SCOPES. :return: a list of classpath strings. """ include_scopes = Scopes.JVM_RUNTIME_SCOPES if include_scopes is None else include_scopes classpath_product = classpath_product or self.context.products.get_data('runtime_classpath') closure = BuildGraph.closure(targets, bfs=True, include_scopes=include_scopes, exclude_scopes=exclude_scopes, respect_intransitive=True) classpath_for_targets = ClasspathUtil.classpath(closure, classpath_product, self.confs) classpath = list(classpath_prefix or ()) classpath.extend(classpath_for_targets) return classpath
def _compute_classpath(runtime_classpath, targets): closure = BuildGraph.closure( targets, bfs=True, include_scopes=Scopes.JVM_RUNTIME_SCOPES, respect_intransitive=True ) classpath_for_targets = ClasspathUtil.classpath(closure, runtime_classpath) return classpath_for_targets
def classpath(self, targets, classpath_prefix=None, classpath_product=None): """Builds a transitive classpath for the given targets. Optionally includes a classpath prefix or building from a non-default classpath product. :param targets: the targets for which to build the transitive classpath. :param classpath_prefix: optional additional entries to prepend to the classpath. :param classpath_product: an optional ClasspathProduct from which to build the classpath. if not specified, the runtime_classpath will be used. :return: a list of classpath strings. """ classpath = list(classpath_prefix) if classpath_prefix else [] classpath_product = classpath_product or self.context.products.get_data( 'runtime_classpath') closure = BuildGraph.closure(targets, bfs=True) classpath_for_targets = ClasspathUtil.classpath( closure, classpath_product, self.confs) classpath.extend(classpath_for_targets) return classpath
def test_closure_bfs(self): root = self.inject_graph( 'a', { 'a': ['b', 'c'], 'b': ['d', 'e'], 'c': ['f', 'g'], 'd': ['h', 'i'], 'e': ['j', 'k'], 'f': ['l', 'm'], 'g': ['n', 'o'], 'h': [], 'i': [], 'j': [], 'k': [], 'l': [], 'm': [], 'n': [], 'o': [], }) bfs_closure = BuildGraph.closure([self.build_graph.get_target(root)], bfs=True) self.assertEquals( [t.address.target_name for t in bfs_closure], [ str(six.unichr(x)) for x in six.moves.xrange(ord('a'), ord('o') + 1) ], )
def test_closure_bfs(self): root = self.inject_graph( "a", { "a": ["b", "c"], "b": ["d", "e"], "c": ["f", "g"], "d": ["h", "i"], "e": ["j", "k"], "f": ["l", "m"], "g": ["n", "o"], "h": [], "i": [], "j": [], "k": [], "l": [], "m": [], "n": [], "o": [], }, ) bfs_closure = BuildGraph.closure([self.build_graph.get_target(root)], bfs=True) self.assertEqual( [t.address.target_name for t in bfs_closure], [chr(x) for x in range(ord("a"), ord("o") + 1)], )
def test_create_bad_targets(self): with self.assertRaises(TypeError): BuildFileAliases(targets={'fred': object()}) target = Target('fred', Address.parse('a:b'), BuildGraph(address_mapper=None)) with self.assertRaises(TypeError): BuildFileAliases(targets={'fred': target})
def setUp(self): """ :API: public """ super(BaseTest, self).setUp() Goal.clear() Subsystem.reset() self.real_build_root = BuildRoot().path self.build_root = os.path.realpath(mkdtemp(suffix='_BUILD_ROOT')) self.addCleanup(safe_rmtree, self.build_root) self.pants_workdir = os.path.join(self.build_root, '.pants.d') safe_mkdir(self.pants_workdir) self.options = defaultdict(dict) # scope -> key-value mapping. self.options[''] = { 'pants_workdir': self.pants_workdir, 'pants_supportdir': os.path.join(self.build_root, 'build-support'), 'pants_distdir': os.path.join(self.build_root, 'dist'), 'pants_configdir': os.path.join(self.build_root, 'config'), 'cache_key_gen_version': '0-test', } self.options['cache'] = { 'read_from': [], 'write_to': [], } BuildRoot().path = self.build_root self.addCleanup(BuildRoot().reset) self._build_configuration = BuildConfiguration() self._build_configuration.register_aliases(self.alias_groups) self.build_file_parser = BuildFileParser(self._build_configuration, self.build_root) self.project_tree = FileSystemProjectTree(self.build_root) self.address_mapper = BuildFileAddressMapper( self.build_file_parser, self.project_tree, build_ignore_patterns=self.build_ignore_patterns) self.build_graph = BuildGraph(address_mapper=self.address_mapper)
def test_closure_bfs(self): root = self.inject_graph('a', { 'a': ['b', 'c'], 'b': ['d', 'e'], 'c': ['f', 'g'], 'd': ['h', 'i'], 'e': ['j', 'k'], 'f': ['l', 'm'], 'g': ['n', 'o'], 'h': [], 'i': [], 'j': [], 'k': [], 'l': [], 'm': [], 'n': [], 'o': [], }) bfs_closure = BuildGraph.closure([self.build_graph.get_target(root)], bfs=True) self.assertEquals( [t.address.target_name for t in bfs_closure], [str(six.unichr(x)) for x in six.moves.xrange(ord('a'), ord('o') + 1)], )
def __init__(self, root_dir, options, build_config, run_tracker, reporting, exiter=sys.exit): """ :param str root_dir: The root directory of the pants workspace (aka the "build root"). :param Options options: The global, pre-initialized Options instance. :param BuildConfiguration build_config: A pre-initialized BuildConfiguration instance. :param Runtracker run_tracker: The global, pre-initialized/running RunTracker instance. :param Reporting reporting: The global, pre-initialized Reporting instance. :param func exiter: A function that accepts an exit code value and exits (for tests, Optional). """ self._root_dir = root_dir self._options = options self._build_config = build_config self._run_tracker = run_tracker self._reporting = reporting self._exiter = exiter self._goals = [] self._targets = [] self._requested_goals = self._options.goals self._target_specs = self._options.target_specs self._help_request = self._options.help_request self._global_options = options.for_global_scope() self._tag = self._global_options.tag self._fail_fast = self._global_options.fail_fast # Will be provided through context.address_mapper.build_ignore_patterns. self._spec_excludes = None self._explain = self._global_options.explain self._kill_nailguns = self._global_options.kill_nailguns self._project_tree = self._get_project_tree(self._global_options.build_file_rev) self._build_file_parser = BuildFileParser(self._build_config, self._root_dir) build_ignore_patterns = self._global_options.ignore_patterns or [] build_ignore_patterns.extend(BuildFile._spec_excludes_to_gitignore_syntax(self._root_dir, self._global_options.spec_excludes)) self._address_mapper = BuildFileAddressMapper(self._build_file_parser, self._project_tree, build_ignore_patterns) self._build_graph = BuildGraph(self._address_mapper) self._spec_parser = CmdLineSpecParser( self._root_dir, self._address_mapper, spec_excludes=self._spec_excludes, exclude_target_regexps=self._global_options.exclude_target_regexp )
def test_closure(self): self.assertEqual(OrderedSet(), BuildGraph.closure([])) a = self.make_target("a") self.assertEqual(OrderedSet([a]), BuildGraph.closure([a])) b = self.make_target("b", dependencies=[a]) self.assertEqual(OrderedSet([b, a]), BuildGraph.closure([b])) c = self.make_target("c", dependencies=[b]) self.assertEqual(OrderedSet([c, b, a]), BuildGraph.closure([c])) d = self.make_target("d", dependencies=[a, c]) self.assertEqual(OrderedSet([d, a, c, b]), BuildGraph.closure([d])) def d_gen(): yield d self.assertEqual(OrderedSet([d, a, c, b]), BuildGraph.closure(d_gen())) empty_gen = iter(()) self.assertEqual(OrderedSet([]), BuildGraph.closure(empty_gen))
def classpath(self, targets, classpath_prefix=None, classpath_product=None): """Builds a transitive classpath for the given targets. Optionally includes a classpath prefix or building from a non-default classpath product. :param targets: the targets for which to build the transitive classpath. :param classpath_prefix: optional additional entries to prepend to the classpath. :param classpath_product: an optional ClasspathProduct from which to build the classpath. if not specified, the runtime_classpath will be used. :return: a list of classpath strings. """ classpath = list(classpath_prefix) if classpath_prefix else [] classpath_product = classpath_product or self.context.products.get_data('runtime_classpath') closure = BuildGraph.closure(targets, bfs=True) classpath_for_targets = ClasspathUtil.classpath(closure, classpath_product, self.confs) classpath.extend(classpath_for_targets) return classpath
def test_closure(self): self.assertEquals([], BuildGraph.closure([])) a = self.make_target('a') self.assertEquals([a], BuildGraph.closure([a])) b = self.make_target('b', dependencies=[a]) self.assertEquals([b, a], BuildGraph.closure([b])) c = self.make_target('c', dependencies=[b]) self.assertEquals([c, b, a], BuildGraph.closure([c])) d = self.make_target('d', dependencies=[a, c]) self.assertEquals([d, a, c, b], BuildGraph.closure([d])) def d_gen(): yield d self.assertEquals([d, a, c, b], BuildGraph.closure(d_gen())) def empty_gen(): return yield self.assertEquals([], BuildGraph.closure(empty_gen()))
def test_closure(self): self.assertEquals([], BuildGraph.closure([])) a = self.make_target('a') self.assertEquals([a], BuildGraph.closure([a])) b = self.make_target('b', dependencies=[a]) self.assertEquals([b, a], BuildGraph.closure([b])) c = self.make_target('c', dependencies=[b]) self.assertEquals([c, b, a], BuildGraph.closure([c])) d = self.make_target('d', dependencies=[a, c]) self.assertEquals([d, a, c, b], BuildGraph.closure([d])) def d_gen(): yield d self.assertEquals([d, a, c, b], BuildGraph.closure(d_gen())) def empty_gen(): return yield self.assertEquals([], BuildGraph.closure(empty_gen()))
def setUp(self): """ :API: public """ super(BaseTest, self).setUp() Goal.clear() Subsystem.reset() self.real_build_root = BuildRoot().path self.build_root = os.path.realpath(mkdtemp(suffix='_BUILD_ROOT')) self.addCleanup(safe_rmtree, self.build_root) self.pants_workdir = os.path.join(self.build_root, '.pants.d') safe_mkdir(self.pants_workdir) self.options = defaultdict(dict) # scope -> key-value mapping. self.options[''] = { 'pants_workdir': self.pants_workdir, 'pants_supportdir': os.path.join(self.build_root, 'build-support'), 'pants_distdir': os.path.join(self.build_root, 'dist'), 'pants_configdir': os.path.join(self.build_root, 'config'), 'cache_key_gen_version': '0-test', } self.options['cache'] = { 'read_from': [], 'write_to': [], } BuildRoot().path = self.build_root self.addCleanup(BuildRoot().reset) self._build_configuration = BuildConfiguration() self._build_configuration.register_aliases(self.alias_groups) self.build_file_parser = BuildFileParser(self._build_configuration, self.build_root) self.project_tree = FileSystemProjectTree(self.build_root) self.address_mapper = BuildFileAddressMapper(self.build_file_parser, self.project_tree, build_ignore_patterns=self.build_ignore_patterns) self.build_graph = BuildGraph(address_mapper=self.address_mapper)
def test_closure(self): self.assertEqual([], BuildGraph.closure([])) a = self.make_target('a') self.assertEqual([a], BuildGraph.closure([a])) b = self.make_target('b', dependencies=[a]) self.assertEqual([b, a], BuildGraph.closure([b])) c = self.make_target('c', dependencies=[b]) self.assertEqual([c, b, a], BuildGraph.closure([c])) d = self.make_target('d', dependencies=[a, c]) self.assertEqual([d, a, c, b], BuildGraph.closure([d])) def d_gen(): yield d self.assertEqual([d, a, c, b], BuildGraph.closure(d_gen())) def empty_gen(): return yield # type: ignore[misc] # MyPy complains that this is not reachable self.assertEqual([], BuildGraph.closure(empty_gen()))
class GoalRunnerFactory(object): def __init__(self, root_dir, options, build_config, run_tracker, reporting, exiter=sys.exit): """ :param str root_dir: The root directory of the pants workspace (aka the "build root"). :param Options options: The global, pre-initialized Options instance. :param BuildConfiguration build_config: A pre-initialized BuildConfiguration instance. :param Runtracker run_tracker: The global, pre-initialized/running RunTracker instance. :param Reporting reporting: The global, pre-initialized Reporting instance. :param func exiter: A function that accepts an exit code value and exits (for tests, Optional). """ self._root_dir = root_dir self._options = options self._build_config = build_config self._run_tracker = run_tracker self._reporting = reporting self._exiter = exiter self._goals = [] self._targets = [] self._requested_goals = self._options.goals self._target_specs = self._options.target_specs self._help_request = self._options.help_request self._global_options = options.for_global_scope() self._tag = self._global_options.tag self._fail_fast = self._global_options.fail_fast self._spec_excludes = self._global_options.spec_excludes self._explain = self._global_options.explain self._kill_nailguns = self._global_options.kill_nailguns self._project_tree = self._get_project_tree(self._global_options.build_file_rev) self._build_file_parser = BuildFileParser(self._build_config, self._root_dir) self._address_mapper = BuildFileAddressMapper(self._build_file_parser, self._project_tree) self._build_graph = BuildGraph(self._address_mapper) self._spec_parser = CmdLineSpecParser( self._root_dir, self._address_mapper, spec_excludes=self._spec_excludes, exclude_target_regexps=self._global_options.exclude_target_regexp, ) def _get_project_tree(self, build_file_rev): """Creates the project tree for build files for use in a given pants run.""" if build_file_rev: return ScmProjectTree(self._root_dir, get_scm(), build_file_rev) else: return FileSystemProjectTree(self._root_dir) def _expand_goals(self, goals): """Check and populate the requested goals for a given run.""" for goal in goals: try: self._address_mapper.resolve_spec(goal) logger.warning( "Command-line argument '{0}' is ambiguous and was assumed to be " "a goal. If this is incorrect, disambiguate it with ./{0}.".format(goal) ) except AddressLookupError: pass if self._help_request: help_printer = HelpPrinter(self._options) result = help_printer.print_help() self._exiter(result) self._goals.extend([Goal.by_name(goal) for goal in goals]) def _expand_specs(self, specs, fail_fast): """Populate the BuildGraph and target list from a set of input specs.""" with self._run_tracker.new_workunit(name="parse", labels=[WorkUnitLabel.SETUP]): def filter_for_tag(tag): return lambda target: tag in map(str, target.tags) tag_filter = wrap_filters(create_filters(self._tag, filter_for_tag)) for spec in specs: for address in self._spec_parser.parse_addresses(spec, fail_fast): self._build_graph.inject_address_closure(address) target = self._build_graph.get_target(address) if tag_filter(target): self._targets.append(target) def _maybe_launch_pantsd(self): """Launches pantsd if configured to do so.""" if self._global_options.enable_pantsd: # Avoid runtracker output if pantsd is disabled. Otherwise, show up to inform the user its on. with self._run_tracker.new_workunit(name="pantsd", labels=[WorkUnitLabel.SETUP]): PantsDaemonLauncher.global_instance().maybe_launch() def _is_quiet(self): return any(goal.has_task_of_type(QuietTaskMixin) for goal in self._goals) or self._explain def _setup_context(self): self._maybe_launch_pantsd() with self._run_tracker.new_workunit(name="setup", labels=[WorkUnitLabel.SETUP]): self._expand_goals(self._requested_goals) self._expand_specs(self._target_specs, self._fail_fast) # Now that we've parsed the bootstrap BUILD files, and know about the SCM system. self._run_tracker.run_info.add_scm_info() # Update the Reporting settings now that we have options and goal info. invalidation_report = self._reporting.update_reporting( self._global_options, self._is_quiet(), self._run_tracker ) context = Context( options=self._options, run_tracker=self._run_tracker, target_roots=self._targets, requested_goals=self._requested_goals, build_graph=self._build_graph, build_file_parser=self._build_file_parser, address_mapper=self._address_mapper, spec_excludes=self._spec_excludes, invalidation_report=invalidation_report, ) return context, invalidation_report def setup(self): context, invalidation_report = self._setup_context() return GoalRunner( context=context, goals=self._goals, kill_nailguns=self._kill_nailguns, run_tracker=self._run_tracker, invalidation_report=invalidation_report, exiter=self._exiter, )
class GoalRunnerFactory(object): def __init__(self, root_dir, options, build_config, run_tracker, reporting, exiter=sys.exit): """ :param str root_dir: The root directory of the pants workspace (aka the "build root"). :param Options options: The global, pre-initialized Options instance. :param BuildConfiguration build_config: A pre-initialized BuildConfiguration instance. :param Runtracker run_tracker: The global, pre-initialized/running RunTracker instance. :param Reporting reporting: The global, pre-initialized Reporting instance. :param func exiter: A function that accepts an exit code value and exits (for tests, Optional). """ self._root_dir = root_dir self._options = options self._build_config = build_config self._run_tracker = run_tracker self._reporting = reporting self._exiter = exiter self._goals = [] self._targets = [] self._requested_goals = self._options.goals self._target_specs = self._options.target_specs self._help_request = self._options.help_request self._global_options = options.for_global_scope() self._tag = self._global_options.tag self._fail_fast = self._global_options.fail_fast self._spec_excludes = self._global_options.spec_excludes self._explain = self._global_options.explain self._kill_nailguns = self._global_options.kill_nailguns self._build_file_type = self._get_buildfile_type( self._global_options.build_file_rev) self._build_file_parser = BuildFileParser(self._build_config, self._root_dir) self._address_mapper = BuildFileAddressMapper(self._build_file_parser, self._build_file_type) self._build_graph = BuildGraph(self._address_mapper) self._spec_parser = CmdLineSpecParser( self._root_dir, self._address_mapper, spec_excludes=self._spec_excludes, exclude_target_regexps=self._global_options.exclude_target_regexp) def _get_buildfile_type(self, build_file_rev): """Selects the BuildFile type for use in a given pants run.""" if build_file_rev: ScmBuildFile.set_rev(build_file_rev) ScmBuildFile.set_scm(get_scm()) return ScmBuildFile else: return FilesystemBuildFile def _expand_goals(self, goals): """Check and populate the requested goals for a given run.""" for goal in goals: if self._address_mapper.from_cache(self._root_dir, goal, must_exist=False).file_exists(): logger.warning( "Command-line argument '{0}' is ambiguous and was assumed to be " "a goal. If this is incorrect, disambiguate it with ./{0}." .format(goal)) if self._help_request: help_printer = HelpPrinter(self._options) help_printer.print_help() self._exiter(0) self._goals.extend([Goal.by_name(goal) for goal in goals]) def _expand_specs(self, specs, fail_fast): """Populate the BuildGraph and target list from a set of input specs.""" with self._run_tracker.new_workunit(name='parse', labels=[WorkUnitLabel.SETUP]): def filter_for_tag(tag): return lambda target: tag in map(str, target.tags) tag_filter = wrap_filters(create_filters(self._tag, filter_for_tag)) for spec in specs: for address in self._spec_parser.parse_addresses( spec, fail_fast): self._build_graph.inject_address_closure(address) target = self._build_graph.get_target(address) if tag_filter(target): self._targets.append(target) def _is_quiet(self): return any( goal.has_task_of_type(QuietTaskMixin) for goal in self._goals) or self._explain def _setup_context(self): # TODO(John Sirois): Kill when source root registration is lifted out of BUILD files. with self._run_tracker.new_workunit(name='bootstrap', labels=[WorkUnitLabel.SETUP]): source_root_bootstrapper = SourceRootBootstrapper.global_instance() source_root_bootstrapper.bootstrap(self._address_mapper, self._build_file_parser) with self._run_tracker.new_workunit(name='setup', labels=[WorkUnitLabel.SETUP]): self._expand_goals(self._requested_goals) self._expand_specs(self._target_specs, self._fail_fast) # Now that we've parsed the bootstrap BUILD files, and know about the SCM system. self._run_tracker.run_info.add_scm_info() # Update the Reporting settings now that we have options and goal info. invalidation_report = self._reporting.update_reporting( self._global_options, self._is_quiet(), self._run_tracker) context = Context(options=self._options, run_tracker=self._run_tracker, target_roots=self._targets, requested_goals=self._requested_goals, build_graph=self._build_graph, build_file_parser=self._build_file_parser, address_mapper=self._address_mapper, spec_excludes=self._spec_excludes, invalidation_report=invalidation_report) return context, invalidation_report def setup(self): context, invalidation_report = self._setup_context() return GoalRunner(context=context, goals=self._goals, kill_nailguns=self._kill_nailguns, run_tracker=self._run_tracker, invalidation_report=invalidation_report)
def test_lookup_exception(self): # There is code that depends on the fact that TransitiveLookupError is a subclass of # AddressLookupError self.assertIsInstance(BuildGraph.TransitiveLookupError(), AddressLookupError)
class GoalRunnerFactory(object): def __init__(self, root_dir, options, build_config, run_tracker, reporting, exiter=sys.exit): """ :param str root_dir: The root directory of the pants workspace (aka the "build root"). :param Options options: The global, pre-initialized Options instance. :param BuildConfiguration build_config: A pre-initialized BuildConfiguration instance. :param Runtracker run_tracker: The global, pre-initialized/running RunTracker instance. :param Reporting reporting: The global, pre-initialized Reporting instance. :param func exiter: A function that accepts an exit code value and exits (for tests, Optional). """ self._root_dir = root_dir self._options = options self._build_config = build_config self._run_tracker = run_tracker self._reporting = reporting self._exiter = exiter self._goals = [] self._targets = [] self._requested_goals = self._options.goals self._target_specs = self._options.target_specs self._help_request = self._options.help_request self._global_options = options.for_global_scope() self._tag = self._global_options.tag self._fail_fast = self._global_options.fail_fast self._spec_excludes = self._global_options.spec_excludes self._explain = self._global_options.explain self._kill_nailguns = self._global_options.kill_nailguns self._build_file_type = self._get_buildfile_type(self._global_options.build_file_rev) self._build_file_parser = BuildFileParser(self._build_config, self._root_dir) self._address_mapper = BuildFileAddressMapper(self._build_file_parser, self._build_file_type) self._build_graph = BuildGraph(self._address_mapper) self._spec_parser = CmdLineSpecParser( self._root_dir, self._address_mapper, spec_excludes=self._spec_excludes, exclude_target_regexps=self._global_options.exclude_target_regexp ) def _get_buildfile_type(self, build_file_rev): """Selects the BuildFile type for use in a given pants run.""" if build_file_rev: ScmBuildFile.set_rev(build_file_rev) ScmBuildFile.set_scm(get_scm()) return ScmBuildFile else: return FilesystemBuildFile def _expand_goals(self, goals): """Check and populate the requested goals for a given run.""" for goal in goals: if self._address_mapper.from_cache(self._root_dir, goal, must_exist=False).file_exists(): logger.warning("Command-line argument '{0}' is ambiguous and was assumed to be " "a goal. If this is incorrect, disambiguate it with ./{0}.".format(goal)) if self._help_request: help_printer = HelpPrinter(self._options) help_printer.print_help() self._exiter(0) self._goals.extend([Goal.by_name(goal) for goal in goals]) def _expand_specs(self, specs, fail_fast): """Populate the BuildGraph and target list from a set of input specs.""" with self._run_tracker.new_workunit(name='parse', labels=[WorkUnitLabel.SETUP]): def filter_for_tag(tag): return lambda target: tag in map(str, target.tags) tag_filter = wrap_filters(create_filters(self._tag, filter_for_tag)) for spec in specs: for address in self._spec_parser.parse_addresses(spec, fail_fast): self._build_graph.inject_address_closure(address) target = self._build_graph.get_target(address) if tag_filter(target): self._targets.append(target) def _is_quiet(self): return any(goal.has_task_of_type(QuietTaskMixin) for goal in self._goals) or self._explain def _setup_context(self): # TODO(John Sirois): Kill when source root registration is lifted out of BUILD files. with self._run_tracker.new_workunit(name='bootstrap', labels=[WorkUnitLabel.SETUP]): source_root_bootstrapper = SourceRootBootstrapper.global_instance() source_root_bootstrapper.bootstrap(self._address_mapper, self._build_file_parser) with self._run_tracker.new_workunit(name='setup', labels=[WorkUnitLabel.SETUP]): self._expand_goals(self._requested_goals) self._expand_specs(self._target_specs, self._fail_fast) # Now that we've parsed the bootstrap BUILD files, and know about the SCM system. self._run_tracker.run_info.add_scm_info() # Update the Reporting settings now that we have options and goal info. invalidation_report = self._reporting.update_reporting(self._global_options, self._is_quiet(), self._run_tracker) context = Context(options=self._options, run_tracker=self._run_tracker, target_roots=self._targets, requested_goals=self._requested_goals, build_graph=self._build_graph, build_file_parser=self._build_file_parser, address_mapper=self._address_mapper, spec_excludes=self._spec_excludes, invalidation_report=invalidation_report) return context, invalidation_report def setup(self): context, invalidation_report = self._setup_context() return GoalRunner(context=context, goals=self._goals, kill_nailguns=self._kill_nailguns, run_tracker=self._run_tracker, invalidation_report=invalidation_report)
class GoalRunnerFactory(object): def __init__(self, root_dir, options, build_config, run_tracker, reporting, exiter=sys.exit): """ :param str root_dir: The root directory of the pants workspace (aka the "build root"). :param Options options: The global, pre-initialized Options instance. :param BuildConfiguration build_config: A pre-initialized BuildConfiguration instance. :param Runtracker run_tracker: The global, pre-initialized/running RunTracker instance. :param Reporting reporting: The global, pre-initialized Reporting instance. :param func exiter: A function that accepts an exit code value and exits (for tests, Optional). """ self._root_dir = root_dir self._options = options self._build_config = build_config self._run_tracker = run_tracker self._reporting = reporting self._exiter = exiter self._goals = [] self._targets = [] self._requested_goals = self._options.goals self._target_specs = self._options.target_specs self._help_request = self._options.help_request self._global_options = options.for_global_scope() self._tag = self._global_options.tag self._fail_fast = self._global_options.fail_fast # Will be provided through context.address_mapper.build_ignore_patterns. self._spec_excludes = None self._explain = self._global_options.explain self._kill_nailguns = self._global_options.kill_nailguns self._project_tree = self._get_project_tree(self._global_options.build_file_rev) self._build_file_parser = BuildFileParser(self._build_config, self._root_dir) build_ignore_patterns = self._global_options.ignore_patterns or [] build_ignore_patterns.extend(BuildFile._spec_excludes_to_gitignore_syntax(self._root_dir, self._global_options.spec_excludes)) self._address_mapper = BuildFileAddressMapper( self._build_file_parser, self._project_tree, build_ignore_patterns, exclude_target_regexps=self._global_options.exclude_target_regexp ) self._build_graph = BuildGraph(self._address_mapper) def _get_project_tree(self, build_file_rev): """Creates the project tree for build files for use in a given pants run.""" if build_file_rev: return ScmProjectTree(self._root_dir, get_scm(), build_file_rev) else: return FileSystemProjectTree(self._root_dir) def _expand_goals(self, goals): """Check and populate the requested goals for a given run.""" for goal in goals: try: self._address_mapper.resolve_spec(goal) logger.warning("Command-line argument '{0}' is ambiguous and was assumed to be " "a goal. If this is incorrect, disambiguate it with ./{0}.".format(goal)) except AddressLookupError: pass if self._help_request: help_printer = HelpPrinter(self._options) result = help_printer.print_help() self._exiter(result) self._goals.extend([Goal.by_name(goal) for goal in goals]) def _expand_specs(self, spec_strs, fail_fast): """Populate the BuildGraph and target list from a set of input specs.""" with self._run_tracker.new_workunit(name='parse', labels=[WorkUnitLabel.SETUP]): def filter_for_tag(tag): return lambda target: tag in map(str, target.tags) tag_filter = wrap_filters(create_filters(self._tag, filter_for_tag)) # Parse all specs into unique Spec objects. spec_parser = CmdLineSpecParser(self._root_dir) specs = OrderedSet() for spec_str in spec_strs: specs.add(spec_parser.parse_spec(spec_str)) # Then scan them to generate unique Addresses. for address in self._address_mapper.scan_specs(specs, fail_fast, self._spec_excludes): self._build_graph.inject_address_closure(address) target = self._build_graph.get_target(address) if tag_filter(target): self._targets.append(target) def _maybe_launch_pantsd(self): """Launches pantsd if configured to do so.""" if self._global_options.enable_pantsd: # Avoid runtracker output if pantsd is disabled. Otherwise, show up to inform the user its on. with self._run_tracker.new_workunit(name='pantsd', labels=[WorkUnitLabel.SETUP]): PantsDaemonLauncher.global_instance().maybe_launch() def _is_quiet(self): return any(goal.has_task_of_type(QuietTaskMixin) for goal in self._goals) or self._explain def _setup_context(self): self._maybe_launch_pantsd() with self._run_tracker.new_workunit(name='setup', labels=[WorkUnitLabel.SETUP]): self._expand_goals(self._requested_goals) self._expand_specs(self._target_specs, self._fail_fast) # Now that we've parsed the bootstrap BUILD files, and know about the SCM system. self._run_tracker.run_info.add_scm_info() # Update the Reporting settings now that we have options and goal info. invalidation_report = self._reporting.update_reporting(self._global_options, self._is_quiet(), self._run_tracker) context = Context(options=self._options, run_tracker=self._run_tracker, target_roots=self._targets, requested_goals=self._requested_goals, build_graph=self._build_graph, build_file_parser=self._build_file_parser, address_mapper=self._address_mapper, spec_excludes=self._spec_excludes, invalidation_report=invalidation_report) return context, invalidation_report def setup(self): context, invalidation_report = self._setup_context() return GoalRunner(context=context, goals=self._goals, kill_nailguns=self._kill_nailguns, run_tracker=self._run_tracker, invalidation_report=invalidation_report, exiter=self._exiter)
def instrument(self, output_dir): for datafile in self._iter_datafiles(output_dir): os.unlink(datafile) self._canonical_datafile = os.path.join(output_dir, '{}.canonical'.format(self._DATAFILE_NAME)) # It's conceivable we'll be executing a test that has no source file dependencies; ie: we'll # never generate a canonical coverage datafile below. Create an empty one here to allow the # test run to proceeed normally. touch(self._canonical_datafile) # Setup an instrumentation classpath based on the existing runtime classpath. runtime_classpath = self._context.products.get_data('runtime_classpath') instrumentation_classpath = self._context.products.safe_create_data('instrument_classpath', runtime_classpath.copy) self.initialize_instrument_classpath(output_dir, self._settings, self._targets, instrumentation_classpath) cobertura_cp = self._settings.tool_classpath('cobertura-instrument') files_to_instrument = [] for target in self._targets: if Cobertura.is_coverage_target(target): paths = instrumentation_classpath.get_for_target(target) for (name, path) in paths: files_to_instrument.append(path) if len(files_to_instrument) > 0: unique_files = list(set(files_to_instrument)) relativize_paths(unique_files, self._settings.workdir) args = [ '--basedir', self._settings.workdir, '--datafile', self._canonical_datafile, ] if self._include_user_classpath: closure = BuildGraph.closure(self._targets, bfs=True, include_scopes=Scopes.JVM_TEST_SCOPES, respect_intransitive=True) aux_classpath = safe_classpath( ClasspathUtil.classpath(closure, runtime_classpath), synthetic_jar_dir=None) args.append('--auxClasspath') args.extend(aux_classpath) # apply class incl/excl filters if len(self._include_classes) > 0: for pattern in self._include_classes: args += ["--includeClasses", pattern] else: args += ["--includeClasses", '.*'] # default to instrumenting all classes for pattern in self._exclude_classes: args += ["--excludeClasses", pattern] with temporary_file() as tmp_file: tmp_file.write("\n".join(unique_files)) tmp_file.flush() args += ["--listOfFilesToInstrument", tmp_file.name] main = 'net.sourceforge.cobertura.instrument.InstrumentMain' self._settings.log.debug( "executing cobertura instrumentation with the following args: {}".format(args)) result = self._execute_java(classpath=cobertura_cp, main=main, jvm_options=self._settings.coverage_jvm_options, args=args, workunit_factory=self._context.new_workunit, workunit_name='cobertura-instrument') if result != 0: raise TaskError("java {0} ... exited non-zero ({1})" " 'failed to instrument'".format(main, result))
def reset_build_graph(self): """Start over with a fresh build graph with no targets in it.""" self.address_mapper = BuildFileAddressMapper(self.build_file_parser, FilesystemBuildFile) self.build_graph = BuildGraph(address_mapper=self.address_mapper)
class BaseTest(unittest.TestCase): """A baseclass useful for tests requiring a temporary buildroot.""" def build_path(self, relpath): """Returns the canonical BUILD file path for the given relative build path.""" if os.path.basename(relpath).startswith('BUILD'): return relpath else: return os.path.join(relpath, 'BUILD') def create_dir(self, relpath): """Creates a directory under the buildroot. relpath: The relative path to the directory from the build root. """ path = os.path.join(self.build_root, relpath) safe_mkdir(path) return path def create_workdir_dir(self, relpath): """Creates a directory under the work directory. relpath: The relative path to the directory from the work directory. """ path = os.path.join(self.pants_workdir, relpath) safe_mkdir(path) return path def create_file(self, relpath, contents='', mode='wb'): """Writes to a file under the buildroot. relpath: The relative path to the file from the build root. contents: A string containing the contents of the file - '' by default.. mode: The mode to write to the file in - over-write by default. """ path = os.path.join(self.build_root, relpath) with safe_open(path, mode=mode) as fp: fp.write(contents) return path def create_workdir_file(self, relpath, contents='', mode='wb'): """Writes to a file under the work directory. relpath: The relative path to the file from the work directory. contents: A string containing the contents of the file - '' by default.. mode: The mode to write to the file in - over-write by default. """ path = os.path.join(self.pants_workdir, relpath) with safe_open(path, mode=mode) as fp: fp.write(contents) return path def add_to_build_file(self, relpath, target): """Adds the given target specification to the BUILD file at relpath. relpath: The relative path to the BUILD file from the build root. target: A string containing the target definition as it would appear in a BUILD file. """ self.create_file(self.build_path(relpath), target, mode='a') cls = self.address_mapper._build_file_type return cls(root_dir=self.build_root, relpath=self.build_path(relpath)) def make_target(self, spec='', target_type=Target, dependencies=None, derived_from=None, **kwargs): """Creates a target and injects it into the test's build graph. :param string spec: The target address spec that locates this target. :param type target_type: The concrete target subclass to create this new target from. :param list dependencies: A list of target instances this new target depends on. :param derived_from: The target this new target was derived from. :type derived_from: :class:`pants.build_graph.target.Target` """ address = Address.parse(spec) target = target_type(name=address.target_name, address=address, build_graph=self.build_graph, **kwargs) dependencies = dependencies or [] self.build_graph.inject_target(target, dependencies=[dep.address for dep in dependencies], derived_from=derived_from) # TODO(John Sirois): This re-creates a little bit too much work done by the BuildGraph. # Fixup the BuildGraph to deal with non BuildFileAddresses better and just leverage it. for traversable_dependency_spec in target.traversable_dependency_specs: traversable_dependency_address = Address.parse(traversable_dependency_spec, relative_to=address.spec_path) traversable_dependency_target = self.build_graph.get_target(traversable_dependency_address) if not traversable_dependency_target: raise ValueError('Tests must make targets for traversable dependency specs ahead of them ' 'being traversed, {} tried to traverse {} which does not exist.' .format(target, traversable_dependency_address)) if traversable_dependency_target not in target.dependencies: self.build_graph.inject_dependency(dependent=target.address, dependency=traversable_dependency_address) target.mark_transitive_invalidation_hash_dirty() return target @property def alias_groups(self): return BuildFileAliases(targets={'target': Target}) def setUp(self): super(BaseTest, self).setUp() Goal.clear() Subsystem.reset() self.real_build_root = BuildRoot().path self.build_root = os.path.realpath(mkdtemp(suffix='_BUILD_ROOT')) self.addCleanup(safe_rmtree, self.build_root) self.pants_workdir = os.path.join(self.build_root, '.pants.d') safe_mkdir(self.pants_workdir) self.options = defaultdict(dict) # scope -> key-value mapping. self.options[''] = { 'pants_workdir': self.pants_workdir, 'pants_supportdir': os.path.join(self.build_root, 'build-support'), 'pants_distdir': os.path.join(self.build_root, 'dist'), 'pants_configdir': os.path.join(self.build_root, 'config'), 'cache_key_gen_version': '0-test', } BuildRoot().path = self.build_root self.addCleanup(BuildRoot().reset) # We need a pants.ini, even if empty. get_buildroot() uses its presence. self.create_file('pants.ini') self._build_configuration = BuildConfiguration() self._build_configuration.register_aliases(self.alias_groups) self.build_file_parser = BuildFileParser(self._build_configuration, self.build_root) self.address_mapper = BuildFileAddressMapper(self.build_file_parser, FilesystemBuildFile) self.build_graph = BuildGraph(address_mapper=self.address_mapper) def buildroot_files(self, relpath=None): """Returns the set of all files under the test build root. :param string relpath: If supplied, only collect files from this subtree. :returns: All file paths found. :rtype: set """ def scan(): for root, dirs, files in os.walk(os.path.join(self.build_root, relpath or '')): for f in files: yield os.path.relpath(os.path.join(root, f), self.build_root) return set(scan()) def reset_build_graph(self): """Start over with a fresh build graph with no targets in it.""" self.address_mapper = BuildFileAddressMapper(self.build_file_parser, FilesystemBuildFile) self.build_graph = BuildGraph(address_mapper=self.address_mapper) def set_options_for_scope(self, scope, **kwargs): self.options[scope].update(kwargs) def context(self, for_task_types=None, options=None, passthru_args=None, target_roots=None, console_outstream=None, workspace=None, for_subsystems=None): # Many tests use source root functionality via the SourceRootConfig.global_instance() # (typically accessed via Target.target_base), so we always set it up, for convenience. optionables = {SourceRootConfig} extra_scopes = set() for_subsystems = for_subsystems or () for subsystem in for_subsystems: if subsystem.options_scope is None: raise TaskError('You must set a scope on your subsystem type before using it in tests.') optionables.add(subsystem) for_task_types = for_task_types or () for task_type in for_task_types: scope = task_type.options_scope if scope is None: raise TaskError('You must set a scope on your task type before using it in tests.') optionables.add(task_type) extra_scopes.update([si.scope for si in task_type.known_scope_infos()]) optionables.update(Subsystem.closure( set([dep.subsystem_cls for dep in task_type.subsystem_dependencies_iter()]) | self._build_configuration.subsystems())) # Now default the option values and override with any caller-specified values. # TODO(benjy): Get rid of the options arg, and require tests to call set_options. options = options.copy() if options else {} for s, opts in self.options.items(): scoped_opts = options.setdefault(s, {}) scoped_opts.update(opts) options = create_options_for_optionables(optionables, extra_scopes=extra_scopes, options=options) Subsystem._options = options context = create_context(options=options, passthru_args=passthru_args, target_roots=target_roots, build_graph=self.build_graph, build_file_parser=self.build_file_parser, address_mapper=self.address_mapper, console_outstream=console_outstream, workspace=workspace) return context def tearDown(self): super(BaseTest, self).tearDown() FilesystemBuildFile.clear_cache() Subsystem.reset() def target(self, spec): """Resolves the given target address to a Target object. address: The BUILD target address to resolve. Returns the corresponding Target or else None if the address does not point to a defined Target. """ address = Address.parse(spec) self.build_graph.inject_address_closure(address) return self.build_graph.get_target(address) def targets(self, spec): """Resolves a target spec to one or more Target objects. spec: Either BUILD target address or else a target glob using the siblings ':' or descendants '::' suffixes. Returns the set of all Targets found. """ spec_parser = CmdLineSpecParser(self.build_root, self.address_mapper) addresses = list(spec_parser.parse_addresses(spec)) for address in addresses: self.build_graph.inject_address_closure(address) targets = [self.build_graph.get_target(address) for address in addresses] return targets def create_files(self, path, files): """Writes to a file under the buildroot with contents same as file name. path: The relative path to the file from the build root. files: List of file names. """ for f in files: self.create_file(os.path.join(path, f), contents=f) def create_library(self, path, target_type, name, sources=None, **kwargs): """Creates a library target of given type at the BUILD file at path with sources path: The relative path to the BUILD file from the build root. target_type: valid pants target type. name: Name of the library target. sources: List of source file at the path relative to path. **kwargs: Optional attributes that can be set for any library target. Currently it includes support for resources, java_sources, provides and dependencies. """ if sources: self.create_files(path, sources) self.add_to_build_file(path, dedent(''' %(target_type)s(name='%(name)s', %(sources)s %(resources)s %(java_sources)s %(provides)s %(dependencies)s ) ''' % dict(target_type=target_type, name=name, sources=('sources=%s,' % repr(sources) if sources else ''), resources=('resources=["%s"],' % kwargs.get('resources') if 'resources' in kwargs else ''), java_sources=('java_sources=[%s],' % ','.join(map(lambda str_target: '"%s"' % str_target, kwargs.get('java_sources'))) if 'java_sources' in kwargs else ''), provides=('provides=%s,' % kwargs.get('provides') if 'provides' in kwargs else ''), dependencies=('dependencies=%s,' % kwargs.get('dependencies') if 'dependencies' in kwargs else ''), ))) return self.target('%s:%s' % (path, name)) def create_resources(self, path, name, *sources): return self.create_library(path, 'resources', name, sources) def assertUnorderedPrefixEqual(self, expected, actual_iter): """Consumes len(expected) items from the given iter, and asserts that they match, unordered.""" actual = list(itertools.islice(actual_iter, len(expected))) self.assertEqual(sorted(expected), sorted(actual)) def assertPrefixEqual(self, expected, actual_iter): """Consumes len(expected) items from the given iter, and asserts that they match, in order.""" self.assertEqual(expected, list(itertools.islice(actual_iter, len(expected))))
def instrument(self, output_dir): for datafile in self._iter_datafiles(output_dir): os.unlink(datafile) self._canonical_datafile = os.path.join( output_dir, '{}.canonical'.format(self._DATAFILE_NAME)) # It's conceivable we'll be executing a test that has no source file dependencies; ie: we'll # never generate a canonical coverage datafile below. Create an empty one here to allow the # test run to proceeed normally. touch(self._canonical_datafile) # Setup an instrumentation classpath based on the existing runtime classpath. runtime_classpath = self._context.products.get_data( 'runtime_classpath') instrumentation_classpath = self._context.products.safe_create_data( 'instrument_classpath', runtime_classpath.copy) self.initialize_instrument_classpath(output_dir, self._settings, self._targets, instrumentation_classpath) cobertura_cp = self._settings.tool_classpath('cobertura-instrument') files_to_instrument = [] for target in self._targets: if Cobertura.is_coverage_target(target): paths = instrumentation_classpath.get_for_target(target) for (name, path) in paths: files_to_instrument.append(path) if len(files_to_instrument) > 0: unique_files = list(set(files_to_instrument)) relativize_paths(unique_files, self._settings.workdir) args = [ '--basedir', self._settings.workdir, '--datafile', self._canonical_datafile, ] if self._include_user_classpath: closure = BuildGraph.closure( self._targets, bfs=True, include_scopes=Scopes.JVM_TEST_SCOPES, respect_intransitive=True) aux_classpath = safe_classpath(ClasspathUtil.classpath( closure, runtime_classpath), synthetic_jar_dir=None) args.append('--auxClasspath') args.extend(aux_classpath) # apply class incl/excl filters if len(self._include_classes) > 0: for pattern in self._include_classes: args += ["--includeClasses", pattern] else: args += ["--includeClasses", '.*'] # default to instrumenting all classes for pattern in self._exclude_classes: args += ["--excludeClasses", pattern] with temporary_file(binary_mode=False) as tmp_file: tmp_file.write("\n".join(unique_files)) tmp_file.flush() args += ["--listOfFilesToInstrument", tmp_file.name] main = 'net.sourceforge.cobertura.instrument.InstrumentMain' self._settings.log.debug( "executing cobertura instrumentation with the following args: {}" .format(args)) result = self._execute_java( classpath=cobertura_cp, main=main, jvm_options=self._settings.coverage_jvm_options, args=args, workunit_factory=self._context.new_workunit, workunit_name='cobertura-instrument') if result != 0: raise TaskError("java {0} ... exited non-zero ({1})" " 'failed to instrument'".format( main, result))