-
-
Notifications
You must be signed in to change notification settings - Fork 638
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Incremental strategy for running/skipping targets via tag #6947
Conversation
dc9777e
to
1911841
Compare
@@ -43,8 +43,6 @@ def attempt(self, explain): | |||
with self._context.new_workunit(name=name, labels=[WorkUnitLabel.TASK], log_config=log_config): | |||
if explain: | |||
self._context.log.debug('Skipping execution of {} in explain mode'.format(name)) | |||
elif task.skip_execution: | |||
self._context.log.info('Skipping {}'.format(name)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ref 1
Need to remove this because if a target says no-...-skip
in the tag, we can only find out at the task level by filtering targets.
src/python/pants/task/task.py
Outdated
if not force_run_targets and self.skip_execution: | ||
self.context.log.info('Skipping by returning empty targets ' | ||
'because task level is skipped and there is no forced run targets') | ||
return [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get_targets
returns empty targets to compensate the removal of Ref 1
@@ -43,21 +43,21 @@ def get_echo(which): | |||
return None | |||
else: | |||
with open(path, 'r') as fp: | |||
return [os.path.basename(x.strip()) for x in fp.readlines()] | |||
return {os.path.basename(x.strip()) for x in fp.readlines()} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed these to sets since the target list ordering isn't consistent.
This is ready for review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Just a couple of small suggestions.
with open(os.path.join(self.workdir, 'output'), 'w') as fp: | ||
fp.write('\n'.join(t.address.spec for t in self.get_targets())) | ||
relevant_targets = self.get_targets() | ||
if relevant_targets: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we not want to write an empty file if relevant_targets is empty? Seems more consistent.
return {os.path.basename(x.strip()) for x in fp.readlines()} | ||
|
||
self.assertEqual(expected_one, get_echo('one'), | ||
'one expected {}, but got {}'.format(expected_one, get_echo('one'))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why add these messages? assertEqual
is supposed to already generate a sensible message.
@@ -55,6 +55,10 @@ class HasSkipOptionMixin(object): | |||
def skip_execution(self): | |||
return self.get_options().skip | |||
|
|||
@property | |||
def supports_skipping_target_by_tag(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd call this can_skip_individual_targets()
, so that it's not so closely bound to the fact that we happen to use tags to express how we choose which targets to skip.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have time today to suggest an alternative, but: this particular model is not going to be portable at all to v2, so I don't think we should land it in this form.
The easiest way to improve portability to v2 would be to incorporate Subsystems in some way... possibly by having Tasks that want to filter their target sets explicitly request a scoped Subsystem that holds the relevant options.
@stuhood when you get a chance, could you elaborate why this is not portable to v2? |
Also depending on the size and the timeline v2 involves, it may also be a possibility to get the feature in first then refactor into something v2 friendly. |
#6880 is out, and should be reviewable/landable in the first two weeks of 2019. When it lands,
Because So as an alternative: rather than using the Task's implicit scope, using an explicitly requested The whole thing might then look like having a Task that wants to opt into this request the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added two small suggestions. Thanks for working on this!
(Disclaimer I don’t have laptop so this feedback is from reading over my phone + I didn’t have time to critically think about the correctness of the implementation)
return relevant_targets | ||
|
||
force_run_tag = 'no-{}.skip'.format(self.options_scope) | ||
force_run_targets = set(t for t in relevant_targets if force_run_tag in t.tags) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can use a set comprehension with {}.
|
||
force_skip_tag = '{}.skip'.format(self.options_scope) | ||
force_skip_targets = set(t for t in relevant_targets if force_skip_tag in t.tags) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto on set comprehension
@stuhood thanks for the pointer. I will try to fiddle around the subsystem option. |
Note: @codealchemy will be looking at this part as well (it'll be a great way to learn the new engine concepts). |
@stuhood in your Subsystem scheme, how does one express "skip task X for target Y"? Using the options scope of the Subsystem? E.g., a tag |
@benjyw : Yep. And And the scoped subsystem might be something like |
Problem and Solution
When folks want to enable any linter/formatter, not all targets can be compliant at the same time, and normally this would be an incremental process.
Therefore this PR introduces a generic mechanism to allow incrementally skip/no-skip target per skippable task via target tags. This requires task to declare:
Behavior
For example with target X:
./pants <task> X
One may think of the tag as the highest ranked value. When a target's tag is specified, e.g.
no-lint.scalafmt.skip
, this target will run as long as the context includes it.What this does not solve
If running a particular task depends on the whole context, say java binary A depends on java lib B, then we mark compile-skip on B via tag, this will inadvertently cause A not to compile. This feature only serves a way to specify whether a target should be skipped, but user is ultimately responsible making the choice.