def test_single_non_test_target(self): bfaddr = BuildFileAddress(None, 'bin', 'some/dir') target_adaptor = PythonBinaryAdaptor(type_alias='python_binary') with self.captured_logging(logging.INFO): # Note that this is not the same error message the end user will see, as we're resolving # union Get requests in run_rule, not the real engine. But this test still asserts that # we error when we expect to error. with self.assertRaisesRegex( AssertionError, r'Rule requested: .* which cannot be satisfied.'): run_rule( coordinator_of_tests, rule_args=[ HydratedTarget(bfaddr.to_address(), target_adaptor, ()), UnionMembership( union_rules={TestTarget: [PythonTestsAdaptor]}), AddressProvenanceMap( bfaddr_to_spec={ bfaddr: SingleAddress(directory='some/dir', name='bin'), }), ], mock_gets=[ MockGet( product_type=TestResult, subject_type=PythonTestsAdaptor, mock=lambda _: TestResult(status=Status.SUCCESS, stdout='foo', stderr=''), ), ], )
def run_lint_rule( *, linters: List[Type[Linter]], targets: List[HydratedTargetWithOrigin], per_target_caching: bool, ) -> Tuple[int, str]: console = MockConsole(use_colors=False) union_membership = UnionMembership( OrderedDict({Linter: OrderedSet(linters)})) result: Lint = run_rule( lint, rule_args=[ console, HydratedTargetsWithOrigins(targets), MockOptions(per_target_caching=per_target_caching), union_membership, ], mock_gets=[ MockGet( product_type=LintResult, subject_type=Linter, mock=lambda linter: linter.lint_result, ), ], union_membership=union_membership, ) return result.exit_code, console.stdout.getvalue()
def fmt(console: Console, targets: HydratedTargets, union_membership: UnionMembership) -> Fmt: results = yield [ Get(FmtResult, TargetWithSources, target.adaptor) for target in targets # TODO: make TargetAdaptor return a 'sources' field with an empty snapshot instead of # raising to remove the hasattr() checks here! if union_membership.is_member(TargetWithSources, target.adaptor) and hasattr(target.adaptor, "sources") ] for result in results: files_content = yield Get(FilesContent, Digest, result.digest) # TODO: This is hacky and inefficient, and should be replaced by using the Workspace type # once that is available on master. # Blocked on: https://github.com/pantsbuild/pants/pull/8329 for file_content in files_content: with Path(get_buildroot(), file_content.path).open('wb') as f: f.write(file_content.content) if result.stdout: console.print_stdout(result.stdout) if result.stderr: console.print_stderr(result.stderr) # Since we ran an ExecuteRequest, any failure would already have interrupted our flow exit_code = 0 yield Fmt(exit_code)
def test_coordinator_python_test(self): addr = Address.parse("some/target") target_adaptor = PythonTestsAdaptor(type_alias='python_tests') with self.captured_logging(logging.INFO): result = run_rule( coordinator_of_tests, rule_args=[ HydratedTarget(addr, target_adaptor, ()), UnionMembership( union_rules={TestTarget: [PythonTestsAdaptor]}), AddressProvenanceMap(bfaddr_to_spec={}), ], mock_gets=[ MockGet( product_type=TestResult, subject_type=PythonTestsAdaptor, mock=lambda _: TestResult( status=Status.FAILURE, stdout='foo', stderr=''), ), ], ) self.assertEqual( result, AddressAndTestResult( addr, TestResult(status=Status.FAILURE, stdout='foo', stderr='')))
def test_globbed_non_test_target(self): bfaddr = BuildFileAddress(None, 'bin', 'some/dir') target_adaptor = PythonBinaryAdaptor(type_alias='python_binary') with self.captured_logging(logging.INFO): result = run_rule( coordinator_of_tests, rule_args=[ HydratedTarget(bfaddr.to_address(), target_adaptor, ()), UnionMembership( union_rules={TestTarget: [PythonTestsAdaptor]}), AddressProvenanceMap( bfaddr_to_spec={ bfaddr: DescendantAddresses(directory='some/dir') }), ], mock_gets=[ MockGet( product_type=TestResult, subject_type=PythonTestsAdaptor, mock=lambda _: TestResult( status=Status.SUCCESS, stdout='foo', stderr=''), ), ], ) self.assertEqual(result, AddressAndTestResult(bfaddr.to_address(), None))
def test_has_fields() -> None: empty_union_membership = UnionMembership({}) tgt = FortranTarget({}, address=Address.parse(":lib")) assert tgt.field_types == (FortranExtensions, FortranSources) assert tgt.class_field_types(union_membership=empty_union_membership) == ( FortranExtensions, FortranSources, ) assert tgt.has_fields([]) is True assert FortranTarget.class_has_fields( [], union_membership=empty_union_membership) is True assert tgt.has_fields([FortranExtensions]) is True assert tgt.has_field(FortranExtensions) is True assert (FortranTarget.class_has_fields( [FortranExtensions], union_membership=empty_union_membership) is True) assert (FortranTarget.class_has_field( FortranExtensions, union_membership=empty_union_membership) is True) assert tgt.has_fields([UnrelatedField]) is False assert tgt.has_field(UnrelatedField) is False assert (FortranTarget.class_has_fields( [UnrelatedField], union_membership=empty_union_membership) is False) assert (FortranTarget.class_has_field( UnrelatedField, union_membership=empty_union_membership) is False) assert tgt.has_fields([FortranExtensions, UnrelatedField]) is False assert (FortranTarget.class_has_fields( [FortranExtensions, UnrelatedField], union_membership=empty_union_membership) is False)
def test_add_custom_fields() -> None: class CustomField(BoolField): alias: ClassVar = "custom_field" default: ClassVar = False union_membership = UnionMembership( {FortranTarget.PluginField: OrderedSet([CustomField])}) tgt_values = {CustomField.alias: True} tgt = FortranTarget(tgt_values, address=Address.parse(":lib"), union_membership=union_membership) assert tgt.field_types == (FortranExtensions, FortranSources, CustomField) assert tgt.core_fields == (FortranExtensions, FortranSources) assert tgt.plugin_fields == (CustomField, ) assert tgt.has_field(CustomField) is True assert FortranTarget.class_field_types( union_membership=union_membership) == ( FortranExtensions, FortranSources, CustomField, ) assert FortranTarget.class_has_field( CustomField, union_membership=union_membership) is True assert tgt[CustomField].value is True default_tgt = FortranTarget({}, address=Address.parse(":default"), union_membership=union_membership) assert default_tgt[CustomField].value is False
def run_fmt_rule(self, *, targets: List[HydratedTarget]) -> Tuple[Fmt, str]: result_digest = self.request_single_product( Digest, InputFilesContent([ FileContent(path=str(self.formatted_file), content=self.formatted_content.encode()) ])) console = MockConsole(use_colors=False) result: Fmt = run_rule( fmt, rule_args=[ console, HydratedTargets(targets), Workspace(self.scheduler), UnionMembership( union_rules={FormatTarget: [PythonTargetAdaptor]}) ], mock_gets=[ MockGet(product_type=AggregatedFmtResults, subject_type=PythonTargetAdaptor, mock=lambda adaptor: AggregatedFmtResults( (FmtResult(digest=result_digest, stdout=f"Formatted `{adaptor.name}`", stderr=""), ), combined_digest=result_digest)), MockGet(product_type=Digest, subject_type=DirectoriesToMerge, mock=lambda _: result_digest), ], ) return result, console.stdout.getvalue()
def __init__( self, unhydrated_values: Dict[str, Any], *, address: Address, # NB: `union_membership` is only optional to facilitate tests. In production, we should # always provide this parameter. This should be safe to do because production code should # rarely directly instantiate Targets and should instead use the engine to request them. union_membership: Optional[UnionMembership] = None, ) -> None: self.address = address self.plugin_fields = self._find_plugin_fields(union_membership or UnionMembership({})) field_values = {} aliases_to_field_types = { field_type.alias: field_type for field_type in self.field_types } for alias, value in unhydrated_values.items(): if alias not in aliases_to_field_types: raise InvalidFieldException( f"Unrecognized field `{alias}={value}` in target {address}. Valid fields for " f"the target type `{self.alias}`: {sorted(aliases_to_field_types.keys())}.", ) field_type = aliases_to_field_types[alias] field_values[field_type] = field_type(value, address=address) # For undefined fields, mark the raw value as None. for field_type in set(self.field_types) - set(field_values.keys()): field_values[field_type] = field_type(raw_value=None, address=address) self.field_values = FrozenDict(field_values)
def test_has_fields() -> None: class UnrelatedField(BoolField): alias: ClassVar = "unrelated" default: ClassVar = False empty_union_membership = UnionMembership({}) tgt = HaskellTarget({}, address=Address.parse(":lib")) assert tgt.has_fields([]) is True assert HaskellTarget.class_has_fields( [], union_membership=empty_union_membership) is True assert tgt.has_fields([HaskellGhcExtensions]) is True assert tgt.has_field(HaskellGhcExtensions) is True assert (HaskellTarget.class_has_fields( [HaskellGhcExtensions], union_membership=empty_union_membership) is True) assert (HaskellTarget.class_has_field( HaskellGhcExtensions, union_membership=empty_union_membership) is True) assert tgt.has_fields([UnrelatedField]) is False assert tgt.has_field(UnrelatedField) is False assert (HaskellTarget.class_has_fields( [UnrelatedField], union_membership=empty_union_membership) is False) assert (HaskellTarget.class_has_field( UnrelatedField, union_membership=empty_union_membership) is False) assert tgt.has_fields([HaskellGhcExtensions, UnrelatedField]) is False assert (HaskellTarget.class_has_fields( [HaskellGhcExtensions, UnrelatedField], union_membership=empty_union_membership) is False)
def is_testable( adaptor_with_origin: TargetAdaptorWithOrigin, *, union_membership: UnionMembership, ) -> bool: is_test_target = union_membership.is_member(TestTarget, adaptor_with_origin) is_not_a_glob = isinstance( adaptor_with_origin.origin, (SingleAddress, FilesystemLiteralSpec) ) return adaptor_with_origin.adaptor.has_sources() and (is_test_target or is_not_a_glob)
def run_test_rule( self, *, test_runner: Type[TestRunner], targets: List[HydratedTargetWithOrigin], debug: bool = False, ) -> Tuple[int, str]: console = MockConsole(use_colors=False) options = MockOptions(debug=debug, run_coverage=False) interactive_runner = InteractiveRunner(self.scheduler) workspace = Workspace(self.scheduler) union_membership = UnionMembership( {TestRunner: OrderedSet([test_runner])}) def mock_coordinator_of_tests( wrapped_test_runner: WrappedTestRunner, ) -> AddressAndTestResult: runner = wrapped_test_runner.runner return AddressAndTestResult( address=runner.adaptor_with_origin.adaptor.address, test_result=runner.test_result, # type: ignore[attr-defined] ) result: Test = run_rule( run_tests, rule_args=[ console, options, interactive_runner, HydratedTargetsWithOrigins(targets), workspace, union_membership, ], mock_gets=[ MockGet( product_type=AddressAndTestResult, subject_type=WrappedTestRunner, mock=lambda wrapped_test_runner: mock_coordinator_of_tests( wrapped_test_runner), ), MockGet( product_type=TestDebugRequest, subject_type=TestRunner, mock=lambda _: TestDebugRequest(self.make_ipr()), ), MockGet( product_type=CoverageReport, subject_type=CoverageDataBatch, mock=lambda _: FilesystemCoverageReport( result_digest=EMPTY_DIRECTORY_DIGEST, directory_to_materialize_to=PurePath("mockety/mock"), report_file=None, ), ), ], union_membership=union_membership, ) return result.exit_code, console.stdout.getvalue()
def run_goal_rules( self, *, options_bootstrapper: OptionsBootstrapper, union_membership: UnionMembership, options: Options, goals: Iterable[str], specs: Specs, ): """Runs @goal_rules sequentially and interactively by requesting their implicit Goal products. For retryable failures, raises scheduler.ExecutionError. :returns: An exit code. """ global_options = options.for_global_scope() console = Console( use_colors=global_options.colors, session=self.scheduler_session if global_options.get("v2_ui") else None, ) workspace = Workspace(self.scheduler_session) interactive_runner = InteractiveRunner(self.scheduler_session) for goal in goals: goal_product = self.goal_map[goal] # NB: We no-op for goals that have no V2 implementation because no relevant backends are # registered. This allows us to safely set `--v1 --v2`, even if no V2 backends are registered. # Once V1 is removed, we might want to reconsider the behavior to instead warn or error when # trying to run something like `./pants run` without any backends registered. is_implemented = union_membership.has_members_for_all( goal_product.subsystem_cls.required_union_implementations) if not is_implemented: continue params = Params( specs.provided_specs, options_bootstrapper, console, workspace, interactive_runner, ) logger.debug( f"requesting {goal_product} to satisfy execution of `{goal}` goal" ) try: exit_code = self.scheduler_session.run_goal_rule( goal_product, params) finally: console.flush() if exit_code != PANTS_SUCCEEDED_EXIT_CODE: return exit_code return PANTS_SUCCEEDED_EXIT_CODE
def is_lintable(target_adaptor: TargetAdaptor, *, union_membership: UnionMembership) -> bool: return ( union_membership.is_member(LintTarget, target_adaptor) # TODO: make TargetAdaptor return a 'sources' field with an empty snapshot instead of # raising to remove the hasattr() checks here! and hasattr(target_adaptor, "sources") and target_adaptor.sources.snapshot.files # i.e., sources is not empty )
def is_testable(target: HydratedTarget, *, union_membership: UnionMembership, provenance_map: AddressProvenanceMap) -> bool: is_valid_target_type = ( provenance_map.is_single_address(target.address) or union_membership.is_member(TestTarget, target.adaptor)) has_sources = hasattr( target.adaptor, "sources") and target.adaptor.sources.snapshot.files return is_valid_target_type and has_sources
def test_list_single() -> None: class CustomField(BoolField): """My custom field! Use this field to... """ alias = "custom_field" required = True tests_target_stdout = run_goal( union_membership=UnionMembership( {FortranTests.PluginField: OrderedSet([CustomField])}), details_target=FortranTests.alias, ) assert tests_target_stdout == dedent("""\ fortran_tests ------------- Tests for Fortran code. This assumes that you use the FRUIT test framework. Valid fields: custom_field type: bool, required My custom field! Use this field to... fortran_version type: str | None, default: None timeout type: int | None, default: None The number of seconds to run before timing out. """) binary_target_stdout = run_goal(details_target=FortranBinary.alias) assert binary_target_stdout == dedent("""\ fortran_binary -------------- Valid fields: archive_format type: '.tar' | '.tgz' | None, default: '.tgz' error_behavior type: 'error' | 'ignore' | 'warn', required fortran_version type: str | None, default: None """)
def run_coordinator_of_tests( self, *, address: Address, origin: Optional[OriginSpec] = None, test_target_type: bool = True, include_sources: bool = True, ) -> AddressAndTestResult: mocked_fileset = EagerFilesetWithSpec( "src", {"globs": []}, snapshot=Snapshot( # TODO: this is not robust to set as an empty digest. Add a test util that provides # some premade snapshots and possibly a generalized make_hydrated_target function. directory_digest=EMPTY_DIRECTORY_DIGEST, files=tuple(["test.py"] if include_sources else []), dirs=(), ), ) adaptor_cls = PythonTestsAdaptor if test_target_type else PythonBinaryAdaptor type_alias = "python_tests" if test_target_type else "python_binary" adaptor = adaptor_cls(address=address, type_alias=type_alias, sources=mocked_fileset) union_membership = UnionMembership(union_rules=OrderedDict( {TestTarget: OrderedSet([PythonTestsAdaptorWithOrigin])})) with self.captured_logging(logging.INFO): result: AddressAndTestResult = run_rule( coordinator_of_tests, rule_args=[ HydratedTargetWithOrigin( target=HydratedTarget(adaptor), origin=(origin or SingleAddress(directory=address.spec_path, name=address.target_name)), ), union_membership, ], mock_gets=[ MockGet( product_type=TestResult, subject_type=TestTarget, mock=lambda _: TestResult( status=Status.SUCCESS, stdout="foo", stderr=""), ), ], union_membership=union_membership, ) return result
def run_goal(*, union_membership: Optional[UnionMembership] = None, details_target: Optional[str] = None) -> str: console = MockConsole(use_colors=False) run_rule( list_target_types, rule_args=[ RegisteredTargetTypes.create( [FortranBinary, FortranLibrary, FortranTests]), union_membership or UnionMembership({}), MockOptions(details=details_target), console, ], ) return cast(str, console.stdout.getvalue())
def run_lint_rule( *, targets: List[HydratedTarget], mock_linter: Callable[[PythonTargetAdaptor], LintResult], ) -> Tuple[Lint, MockConsole]: console = MockConsole(use_colors=False) result: Lint = run_rule( lint, rule_args=[ console, HydratedTargets(targets), UnionMembership(union_rules={TargetWithSources: [PythonTargetAdaptor]}) ], mock_gets=[ MockGet(product_type=LintResult, subject_type=PythonTargetAdaptor, mock=mock_linter), ], ) return result, console
def create(cls, options_bootstrapper, full_init=True) -> "PantsDaemon": """ :param OptionsBootstrapper options_bootstrapper: The bootstrap options. :param bool full_init: Whether or not to fully initialize an engine et al for the purposes of spawning a new daemon. `full_init=False` is intended primarily for lightweight lifecycle checks (since there is a ~1s overhead to initialize the engine). See the impl of `maybe_launch` for an example of the intended usage. """ bootstrap_options = options_bootstrapper.bootstrap_options bootstrap_options_values = bootstrap_options.for_global_scope() # TODO: https://github.com/pantsbuild/pants/issues/3479 watchman = WatchmanLauncher.create( bootstrap_options_values).watchman native: Optional[Native] = None build_root: Optional[str] = None if full_init: build_root = get_buildroot() native = Native() build_config = BuildConfigInitializer.get(options_bootstrapper) legacy_graph_scheduler = EngineInitializer.setup_legacy_graph( native, options_bootstrapper, build_config) services = cls._setup_services( build_root, bootstrap_options_values, legacy_graph_scheduler, watchman, union_membership=UnionMembership( build_config.union_rules()), ) else: services = PantsServices() return PantsDaemon( native=native, build_root=build_root, work_dir=bootstrap_options_values.pants_workdir, log_level=bootstrap_options_values.level, services=services, metadata_base_dir=bootstrap_options_values.pants_subprocessdir, bootstrap_options=bootstrap_options, )
def run_coordinator_of_tests( self, *, address: Address, bfaddr_to_address_spec: Optional[Dict[BuildFileAddress, AddressSpec]] = None, test_target_type: bool = True, include_sources: bool = True, ) -> AddressAndTestResult: mocked_fileset = EagerFilesetWithSpec( "src", {"globs": []}, snapshot=Snapshot( # TODO: this is not robust to set as an empty digest. Add a test util that provides # some premade snapshots and possibly a generalized make_hydrated_target function. directory_digest=EMPTY_DIRECTORY_DIGEST, files=tuple(["test.py"] if include_sources else []), dirs=())) target_adaptor = (PythonTestsAdaptor(type_alias='python_tests', sources=mocked_fileset) if test_target_type else PythonBinaryAdaptor( type_alias='python_binary', sources=mocked_fileset)) with self.captured_logging(logging.INFO): result: AddressAndTestResult = run_rule( coordinator_of_tests, rule_args=[ HydratedTarget(address, target_adaptor, ()), UnionMembership( union_rules={TestTarget: [PythonTestsAdaptor]}), AddressProvenanceMap( bfaddr_to_address_spec=bfaddr_to_address_spec or {}), ], mock_gets=[ MockGet( product_type=TestResult, subject_type=PythonTestsAdaptor, mock=lambda _: TestResult( status=Status.SUCCESS, stdout='foo', stderr=''), ), ], ) return result
def run_fmt_rule( *, targets: List[HydratedTarget], mock_formatter: Callable[[PythonTargetAdaptor], FmtResult], ) -> Tuple[Fmt, MockConsole]: console = MockConsole(use_colors=False) result: Fmt = run_rule( fmt, rule_args=[ console, HydratedTargets(targets), UnionMembership(union_rules={TargetWithSources: [PythonTargetAdaptor]}) ], mock_gets=[ MockGet(product_type=FmtResult, subject_type=PythonTargetAdaptor, mock=mock_formatter), MockGet(product_type=FilesContent, subject_type=Digest, mock=lambda _: FilesContent([])) ], ) return result, console
def coordinator_of_tests( target: HydratedTarget, union_membership: UnionMembership, provenance_map: AddressProvenanceMap) -> AddressAndTestResult: # TODO(#6004): when streaming to live TTY, rely on V2 UI for this information. When not a # live TTY, periodically dump heavy hitters to stderr. See # https://github.com/pantsbuild/pants/issues/6004#issuecomment-492699898. if (provenance_map.is_single_address(target.address) or union_membership.is_member(TestTarget, target.adaptor)): logger.info("Starting tests: {}".format(target.address.reference())) # NB: This has the effect of "casting" a TargetAdaptor to a member of the TestTarget union. # The adaptor will always be a member because of the union membership check above, but if # it were not it would fail at runtime with a useful error message. result = yield Get(TestResult, TestTarget, target.adaptor) logger.info("Tests {}: {}".format( "succeeded" if result.status == Status.SUCCESS else "failed", target.address.reference(), )) else: result = None # Not a test target. yield AddressAndTestResult(target.address, result)
def test_globbed_test_target(self): bfaddr = BuildFileAddress(None, 'tests', 'some/dir') target_adaptor = PythonTestsAdaptor(type_alias='python_tests') with self.captured_logging(logging.INFO): result = run_rule( coordinator_of_tests, HydratedTarget(bfaddr.to_address(), target_adaptor, ()), UnionMembership(union_rules={TestTarget: [PythonTestsAdaptor]}), AddressProvenanceMap(bfaddr_to_spec={ bfaddr: DescendantAddresses(directory='some/dir') }), { (TestResult, PythonTestsAdaptor): lambda _: TestResult(status=Status.SUCCESS, stdout='foo', stderr=''), }) self.assertEqual( result, AddressAndTestResult(bfaddr.to_address(), TestResult(status=Status.SUCCESS, stdout='foo', stderr='')) )
def lint(console: Console, targets: HydratedTargets, union_membership: UnionMembership) -> Lint: results = yield [ Get(LintResult, TargetWithSources, target.adaptor) for target in targets # TODO: make TargetAdaptor return a 'sources' field with an empty snapshot instead of # raising to remove the hasattr() checks here! if union_membership.is_member(TargetWithSources, target.adaptor) and hasattr(target.adaptor, "sources") ] exit_code = 0 for result in results: if result.stdout: console.print_stdout(result.stdout) if result.stderr: console.print_stderr(result.stderr) if result.exit_code != 0: exit_code = result.exit_code yield Lint(exit_code)
def run_lint_rule( *, targets: List[HydratedTarget], mock_linter: Optional[Callable[[PythonTargetAdaptor], LintResult]] = None, ) -> Tuple[Lint, str]: if mock_linter is None: mock_linter = lambda target_adaptor: LintResult( exit_code=1, stdout=f"Linted the target `{target_adaptor.name}`", stderr="" ) console = MockConsole(use_colors=False) result: Lint = run_rule( lint, rule_args=[ console, HydratedTargets(targets), UnionMembership(union_rules={TargetWithSources: [PythonTargetAdaptor]}) ], mock_gets=[ MockGet(product_type=LintResult, subject_type=PythonTargetAdaptor, mock=mock_linter), ], ) return result, console.stdout.getvalue()
def run_fmt_rule( self, *, language_formatters: List[Type[LanguageFormatters]], targets: List[HydratedTargetWithOrigin], result_digest: Digest, per_target_caching: bool, ) -> str: console = MockConsole(use_colors=False) union_membership = UnionMembership( OrderedDict({LanguageFormatters: OrderedSet(language_formatters)})) result: Fmt = run_rule( fmt, rule_args=[ console, HydratedTargetsWithOrigins(targets), MockOptions(per_target_caching=per_target_caching), Workspace(self.scheduler), union_membership, ], mock_gets=[ MockGet( product_type=LanguageFmtResults, subject_type=LanguageFormatters, mock=lambda language_formatters: language_formatters. language_fmt_results, ), MockGet( product_type=Digest, subject_type=DirectoriesToMerge, mock=lambda _: result_digest, ), ], union_membership=union_membership, ) assert result.exit_code == 0 return cast(str, console.stdout.getvalue())
def create( cls, env: Mapping[str, str], options_bootstrapper: OptionsBootstrapper, specs: Optional[Specs] = None, daemon_graph_session: Optional[LegacyGraphSession] = None, ) -> "LocalPantsRunner": """Creates a new LocalPantsRunner instance by parsing options. :param env: The environment (e.g. os.environ) for this run. :param options_bootstrapper: The OptionsBootstrapper instance to reuse. :param specs: The specs for this run, i.e. either the address or filesystem specs. :param daemon_graph_session: The graph helper for this session. """ build_root = get_buildroot() options, build_config = LocalPantsRunner.parse_options( options_bootstrapper) global_options = options.for_global_scope() # This works as expected due to the encapsulated_logger in DaemonPantsRunner and # we don't have to gate logging setup anymore. setup_logging_from_options(global_options) # Option values are usually computed lazily on demand, # but command line options are eagerly computed for validation. for scope in options.scope_to_flags.keys(): options.for_scope(scope) # Verify configs. if global_options.verify_config: options.verify_configs(options_bootstrapper.config) union_membership = UnionMembership(build_config.union_rules()) # If we're running with the daemon, we'll be handed a session from the # resident graph helper - otherwise initialize a new one here. graph_session = (daemon_graph_session if daemon_graph_session else cls._init_graph_session( options_bootstrapper, build_config, options)) if specs is None: global_options = options.for_global_scope() specs = SpecsCalculator.create( options=options, build_root=build_root, session=graph_session.scheduler_session, exclude_patterns=tuple(global_options.exclude_target_regexp), tags=tuple(global_options.tag), ) profile_path = env.get("PANTS_PROFILE") return cls( build_root=build_root, options=options, options_bootstrapper=options_bootstrapper, build_config=build_config, specs=specs, graph_session=graph_session, union_membership=union_membership, is_daemon=daemon_graph_session is not None, profile_path=profile_path, )
def union_membership_singleton() -> UnionMembership: return UnionMembership(build_configuration.union_rules())
def run(self): # Ensure anything referencing sys.argv inherits the Pailgun'd args. sys.argv = self.args # Invoke a Pants run with stdio redirected and a proxied environment. with self.nailgunned_stdio( self.maybe_shutdown_socket, self.env) as finalizer, DaemonExiter.override_global_exiter( self.maybe_shutdown_socket, finalizer), hermetic_environment_as( **self.env), encapsulated_global_logger(): exit_code = PANTS_SUCCEEDED_EXIT_CODE try: # Clean global state. clean_global_runtime_state(reset_subsystem=True) options_bootstrapper = OptionsBootstrapper.create( args=self.args, env=self.env) options, build_config = LocalPantsRunner.parse_options( options_bootstrapper) global_options = options.for_global_scope() session = self.scheduler_service.prepare_graph(options) specs = SpecsCalculator.create( options=options, session=session.scheduler_session, exclude_patterns=tuple( global_options.exclude_target_regexp), tags=tuple(global_options.tag) if global_options.tag else (), ) if options.help_request: help_printer = HelpPrinter( options=options, union_membership=UnionMembership( build_config.union_rules()), ) exit_code = help_printer.print_help() else: exit_code = self.scheduler_service.graph_run_v2( session, specs, options, options_bootstrapper) # self.scheduler_service.graph_run_v2 will already run v2 or ambiguous goals. We should # only enter this code path if v1 is set. if global_options.v1: with ExceptionSink.exiter_as_until_exception( lambda _: PantsRunFailCheckerExiter()): runner = LocalPantsRunner.create( self.env, options_bootstrapper, specs, session) env_start_time = self.env.pop( "PANTSD_RUNTRACKER_CLIENT_START_TIME", None) start_time = float( env_start_time) if env_start_time else None runner.set_start_time(start_time) runner.run() except KeyboardInterrupt: self._exiter.exit_and_fail("Interrupted by user.\n") except _PantsRunFinishedWithFailureException as e: ExceptionSink.log_exception( "Pants run failed with exception: {}; exiting".format(e)) self._exiter.exit(e.exit_code) except Exception as e: # TODO: We override sys.excepthook above when we call ExceptionSink.set_exiter(). That # excepthook catches `SignalHandledNonLocalExit`s from signal handlers, which isn't # happening here, so something is probably overriding the excepthook. By catching Exception # and calling this method, we emulate the normal, expected sys.excepthook override. ExceptionSink._log_unhandled_exception_and_exit(exc=e) else: self._exiter.exit(exit_code)